Right now, you can do this:
fn foo() -> impl Trait {
... // the return value must implement Trait
}
In this issue I suggest that we add this syntax as well:
type FooRet = impl Trait;
fn foo() -> FooRet {
... // the return value must implement Trait
}
This means that later we can use FooRet to designate the type that foo() returns.
This makes it possible to put it in a struct for example:
struct Bar {
foo: FooRet,
}
impl Bar {
fn new() -> Bar {
Bar { foo: foo() }
}
}
Handling template parameters looks straight-forward (type Foo<R> = impl Trait; fn foo<R>() -> Foo<R>), but I'm not totally sure that there's a not problem with that.
Add a syntax that resolves the return type of a function, similar to C++'s decltype.
While the feature itself is -I think- mostly straight-forward, the biggest debate is probably the motivation.
Let's take this code:
struct MyIterator { ... }
impl Iterator for MyIterator { ... }
fn get_my_iterator() -> MyIterator { ... }
Right now you can wrap around it, if you want:
struct Wrapper(MyIterator);
fn get_wrapper() -> Wrapper { Wrapper(get_my_iterator() }
This is a zero-cost abstraction and a good usage of composition.
The problem begins when you want to use -> impl Trait:
fn get_my_iterator() -> impl Iterator { ... }
Suddenly, wrapping around it becomes hacky. The primary way to do so is this:
struct Wrapper<I>(I) where I: Iterator;
fn get_wrapper() -> Wrapper<impl Iterator> { Wrapper(get_my_iterator()) }
_(The alternative way is to create a new trait named TraitForWrapper which contains the API of Wrapper, and make get_wrapper() return -> impl TraitForWrapper)_
This not only complicates the documentation and makes the usage of get_wrapper() more confusing for beginners, but it is also leaky, as it makes wrapping around Wrapper more complicated:
struct WrapperWrapper<I>(Wrapper<I>) where I: Iterator;
fn get_wrapper_wrapper() -> WrapperWrapper<impl Iterator> { WrapperWrapper(get_wrapper()) }
_(using the TraitForWrapper method is not better)_
This looks like an hypothetical example, but for example if I were to use -> impl Trait in my project, some equivalents to WrapperWrapper would look nightmarish:
struct RenderSystem<Rp1, Rp2, Rp3, Pl1, Pl2, Pl3> {
fog_of_war_pipeline: GraphicsPipeline<SimpleVertexDefinition, Pl1, Rp1>,
background_pipeline: GraphicsPipeline<SimpleVertexDefinition, Pl2, Rp2>,
lighting_pipeline: GraphicsPipeline<SimpleVertexDefinition, Pl3, Rp3>,
}
Basically, the problem of the -> impl Trait syntax is that it's poisonous. Once you use it in an API, all the codes that use this API will have to use -> impl Trait as well.
This proposition would fix this problem:
type MyIterator = impl Iterator;
fn get_my_iterator() -> MyIterator { ... }
struct Wrapper(MyIterator);
fn get_wrapper() -> Wrapper { Wrapper(get_my_iterator()) }
struct WrapperWrapper(Wrapper);
fn get_wrapper_wrapper() -> WrapperWrapper { WrapperWrapper(get_wrapper()) }
cc @eddyb
My prototype from last year supported impl Trait in associated types (not included in #1522), e.g.:
impl Iterator for Foo {
type Item = impl Iterator<Item=String>;
fn next(&mut self) -> Option<Self::Item> {...}
}
This feature is the minimal extension of #1522 to allow naming impl Trait (i.e. you can always use it to provide top-level type aliases) and it supports all trait usecases that don't involve HKT.
IMO it's a better first step because it's smaller in scope: I side-stepped the problem of choosing which functions to give the ability to determine the impl Trait, by allowing _any_ method in the impl to specify the associated type in its return type, and they had to agree _without_ seeing each-other's choices.
cc @rust-lang/lang
I still would like to name the type in the function signature, so it'd be foo::Output or fn foo()<R> -> R and foo::R or something.
I think this would be better served by abstract type Foo = _ and @eddyb's "independent agreement" approach rather than unification.
@durka We already have syntax for it: <typeof foo>::Output - we just need someone to RFC typeof.
@Ericson2314 I don't see how the proposal goes against my model, the difference with abstract type seems to be strictly syntactical, i.e. impl Trait is more palatable.
Even type Foo; or type Foo: Bar; is nicer and mimics the existing associated type syntax, while abstract doesn't say anything interesting or relevant (since associated types don't also use it).
@eddyb going back to https://github.com/rust-lang/rfcs/pull/105#issuecomment-125418018 I've wanted to separate the inference and abstraction aspects of impl trait. The only reason to keep them separate is avoiding global inference, but as you point out that's actually not necessary.
Because impls are similar to (parametertized) modules, it makes sense to put abstract type in an impl. The semantics are exactly what you say---items in the impl see the real definition but consumers see an abstract type. Similarly, for any item where is concrete, _ instructs that item to infer the type on its own, and only afterwards are the inferred types compared.
What are gotchas around <typeof foo>::Output?
Seems like a more general solution (since you can express original using it) and it applies to more cases eg. where a 3rd party library didn't think it was worth exposing the return type as a type alias. I find it very appealing.
@cristicbz It's not a silver bullet because it gives you the whole type, being able to name an impl Trait is more powerful in that you could have several of them at different levels.
_However_, I do agree that the ability to later name complex and/or anonymous types is good to have.
EDIT: As for gotchas, typeof $expr is not guaranteed to match another copy of typeof $expr.
That is, typeof || {} is different from _any_ typeof || {} other than itself.
It also needs to be isolated wrt inference, so typeof 0 will be i32 (expect maybe in functions?).
The only new thing as far as the implementation is concerned is being able to type-check and infer that expression passed to typeof on-demand, potentially before the items it depends on even have types.
_However_, this is already needed for constants so work is underway to support that mechanism.
There are two possibilities for typeof: either typeof $ident or typeof $expr.
For the former, you'd write something like <typeof foo as Fn>::Output, and for the latter something like typeof foo().
@tomaka Well, it'd have to be a path not an identifier. But yeah, and it could've been implemented in 1.0.
Keep in mind _however_ that even with the expression form you wouldn't want a call if you have arguments.
More generally, you would probably write <typeof(foo::<X,Y,Z>) as Fn>::Output or something like that. (Presuming you had fn foo<A,B,C>() { ... }.)
@eddyb I was thinking of typeof $expr in order to mimic C++'s decltype.
@tomaka So you'd want arguments of the function to be in scope for typeof in the return type?
That could work, it's just more than typeof itself, which would be like an array length except not constant.
I like <typeof foo>::Output or anything similar. And I dislike the original proposal here.
I've almost written :
type FooT = (Meow,Purr,Woof);
fn foo(..) -> FooT {
debug_assert_eq!(mem::size_of::<FooT>(), ..)
...
}
It'd be far cleaner to write simply mem::size_of::<<typeof foo as Fn>::Output>()
Worse, I've needed to write u16::max_value() when I could not trust that the u16 might not change, so I've instead written :
type FooT = u16;
struct Foo(FooT);
And similar situations can arise around any method like T::new() that does not land in traits.
It'd be much cleaner to write Foo.0::max_value() or <typeof i.0>::max_value(). It's not that <typeof ..>:: is so easy to parse, but it'll work in macros. And this type alias trick makes reading the fn or struct signature hard, which seems worse.
Just another idea : I wonder if it's worth adding a name or macro for the current fn. I've no example but a non-example is :
I've previously wanted a version of mem::transmute for pointers that ensured the referenced values had the same size, so roughly :
#[inline]
unsafe fn transmute_ref<A: Deref,B: Deref>(v: A) -> B {
debug_assert_eq!(mem::size_of(A::Target),mem::size_of(B::Target));
let r:B = core::mem::transmute::<A,B>(v);
r
}
I could situations where you wanted to write <current_fn as Fn>::Output in some macro though.
A tricky problem is this:
pub struct Foo {
data: ???
}
impl Foo {
pub fn new() -> Foo {
Foo {
data: (0 .. 3).map(|n| n * 2),
}
}
}
In this example you can't easily use typeof. In order to use typeof you would need to create a separate function just to initialize data, which can be very annoying.
Yes, it'd be nice if Foo.data or Foo::data was simply the type of data as an associated type. Also Foo.0 or Foo::0 for tuples. Otherwise, one writes stuff like let foo: Foo; ... typeof(foo.data) which annoys the compiler because foo never gets used elsewhere. And it's worse at the top level with const declarations going unused.
@burdges but that would collide with method names, right?
struct Foo {
data: u32,
}
impl Foo {
fn data() { ... }
}
Foo::data refers to the method right now. Since types use a different namespaces I suppose you could use Foo::data to refer to the type of the data field rather than the data method when used in type position, but it might end up a little confusing...
@burdges TBH I wouldn't even expect outer declarations to be accessible, at least not in all cases. There is indeed a problem with accessing derived types that don't depend on inputs, and typeof { let x: Foo; x.data } is too ugly.
However, @tomaka's example wouldn't be fixable like that because you'd end up with the field type being equal to... itself. Which is a cyclic type dependency error.
In the complementary any/some model, that example would simply use data: some Iterator<Item=i32>.
My intuition is that a type alias for impl Trait would not be a single type, but would be a distinct type in each instance its used.
For example, I might want to shorten impl Future<Item = T, Error = mycrate::Error> to type Future<T>. Perhaps that behaviour is better handled with trait aliases though.
However, this behavior might also be better handled by allowing users to abstract over for example the return type of some function fn function() -> impl Trait as an associated type, e.g. function::Return.
-> #2071
Seems like the "get the return type of an impl Trait" part of this is still needed despite abstract type, to handle places where you're calling something that didn't use abstract type?
https://users.rust-lang.org/t/how-do-you-store-the-result-of-an-impl-trait-return-type/14808/8
However, this behavior might also be better handled by allowing users to abstract over for example the return type of some function fn function() -> impl Trait as an associated type, e.g. function::Return.
This would be a killer feature. I have a project where i generate an AST via the builder pattern on the type level. Obviously, the type of an AST shouldn't be named by the user; this would be madness for everything more elaborate than a FooBar example (Foo<Bar<Baz<Bez<Biz<Boz<Buz>>>>>>>).
Yet it is desirable to know the type of (and store) an AST. With impl Trait and associated types, we can already choose not the name it, yet know the type, or in this example the serializable AST.
When being able to access the return type of the function, we could actually name and store the AST and derivatives without having to write it out!
Did you mean? Foo<Bar<Baz<Bez<Biz<Boz<Buz>>>>>>
Closing in favor of https://github.com/rust-lang/rfcs/pull/2515.
Most helpful comment
My prototype from last year supported
impl Traitin associated types (not included in #1522), e.g.:This feature is the minimal extension of #1522 to allow naming
impl Trait(i.e. you can always use it to provide top-level type aliases) and it supports all trait usecases that don't involve HKT.IMO it's a better first step because it's smaller in scope: I side-stepped the problem of choosing which functions to give the ability to determine the
impl Trait, by allowing _any_ method in theimplto specify the associated type in its return type, and they had to agree _without_ seeing each-other's choices.cc @rust-lang/lang