Skip to content

Allow explicitly expressing the type of a -> impl Trait #1738

Closed
@tomaka

Description

@tomaka

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()) }

Metadata

Metadata

Assignees

No one assigned

    Labels

    T-langRelevant to the language team, which will review and decide on the RFC.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions