Closure as argument. Parameterization with `impl Trait`

Sure!

@Yandros @H2CO3 @quinedot @alice Any feedback, please.

1 Like

You should give your image a background instead of transparency (or otherwise make it easier to read on both light and dark modes).


You say

Parametrization of the result type with impl Trait solves the problem.
[...]
Is not impl Trait a form of parametrizing a function equivalent? It is, but not identical.

referring to

pub fn problem() -> VectorFormer< impl Fn() > { ... }

and while the return type (VectorFormer<T>) is being parameterized in this case, this still made me pause a bit, as I wouldn't call return-position impl Trait a parameterization of the function in the general case.

// There is only one `f`
fn f() -> impl Display { ... }

I don't know that I would call your "Corner case" section a corner-case. It's more a fundamental property of return position impl Trait -- it's a concrete type, not a dynamic type or generic type [constructor].


Another possible follow-up would be to compare and contrast dyn Trait.


return-position impl Trait (RPIT) is part of a much larger surface area in Rust: type alias impl Trait (TAIT) and generic associated types (GATs). However, those others are not yet stable. But we're getting relatively close.

RPIT in generic context

You can return an impl Trait in a function that has input type parameters:

fn f<T: Debug>(t: T) -> impl Debug {
    vec![t]
}

This means that the opaque, concrete output type can depend on the input type to the function. Basically, the output type is parameterized by the same inputs as the function. Given concrete values for all the inputs, the output type is also concrete.

Introduction to type aliases

In case you didn't know, you can write

type MyInt = i32;

and MyInt will be an alias to i32.

These aliases can be generic too.

type Container<T> = Vec<T>;

Connection of RPIT to TAIT

The RFC mostly uses a different syntax, but basically there will be a way to declare something like

type MyDisplay = impl Display;

And then have a defining use somewhere, but this time it's reusable:

fn f() -> MyDisplay { 0i64 }
// Still a concrete, albeit opaque, type.  They must match.
fn g() -> MyDisplay { 42i64 }

These aliases can be generic, too:

type MyDebug<T: Debug> = impl Debug;
fn h<T: Debug>(t: T) -> MyDebug<T> { vec![t] }

And in fact, the same machinery drives return-position impl Trait today. Those are just generated by the compiler and don't have a name (alias), and are thus not "reusable".

Connection between (non-generic) type aliases and (non-generic) associated types

Associated types of traits are basically an implementation-specific type alias (that can also be bounded on).

trait Foo<T> { type Bar; }
impl Foo<i32> for str { type Bar = f32; }
impl Foo<u32> for str { type Bar = String; }
impl Foo<u32> for String { type Bar = (); }

And you can use this to have a method that has a concrete type per implementation, but may differ across implementations. The type can also be bound.

trait MakeDisplay {
    type Output: Display;
    fn make_it() -> Self::Output;
}

Note that the type must be name-able (so you can write type Output = Name;, and is not opaque to users (who can refer to <Implementer as MakeDisplay>::Output).

Also note that today, unlike type aliases, associated types cannot be generic.

Generic Associated Types

GATs are the natural extension of associated types to be generic, as type aliases are.

I don't know of any specific acronym for it, but another natural extension will be to support full TAITs on GATs. GATAIT?

RPIT in traits (RPITIT?)

Once you have GATs with TAIT, you can support return-position impl Trait within a trait itself. The compiler generates an anonymous GAT defined as an impl Trait within the trait implementation, similar to how RPIT today generates an anonymous TAIT.

Summary

Thing Example Notes Stable?
type alias type X = Y :ballot_box_with_check:
generic type alias type T<X> = Y<X> :ballot_box_with_check:
TAIT type T<X> = impl Tr<X> :x:
RPIT fn f() -> impl Tr anonymous TAIT :ballot_box_with_check:
associated type impl trait Tr for U { type T = Y } :ballot_box_with_check:
GAT impl ... { type T<X> = Y<X> } :x:
GATAIT impl ... { type T<X> = impl Tra<X> } :x:
RPITIT impl ... { fn f(&self) -> impl Tra } anonymous GATAIT :x:
5 Likes

To illustrate:

Very nice post & summary :ok_hand:. This type of "increasing complexity" is begging for a meme:

3 Likes

Thank you :blush: that's so valuable for me that you guys having such tasks pressure found time to review my article.

How would you call it?

how would you call that?

They are both outcomes of RPIT being a

  • type alias
  • of an output type, or of part of an output type
  • in a statically typed language

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.