Description
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() }
}
}
Unresolved questions
- Do we allow multiple functions to return the same "type=impl type" if their effective return type is the same?
- 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.
Alternatives
- Add a syntax that resolves the return type of a function, similar to C++'s
decltype
.
Motivation
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()) }