What is the difference between impl Trait and dyn Trait?

BTW, what is the diffference between dyn Trait and impl Trait when used as types?

9 Likes

Isn’t

fn fun(s:Something)->impl Iterator

the same as

fn fun<T: Iterator> (s:Something)->T

?

1 Like

No. impl Trait can be inferred by the compiler from the function body. In contrast, a generic function receives the type as an input. Ie. you can’t eg. return a closure using a generic type, only impl Trait.

9 Likes

Hmm…, but if only returning one concrete type implementing the trait why not just depose it into the function signature?

The only advantage I see so far is for refactoring, calling overloaded functions with different input types result into different return types such that we don’t have to change the signature each time.

1 Like

The major advantages of impl Trait are:

  • the underlying concrete type can be an unnameable type, such as a closure
    • in particular, this makes it possible to write functions that return closures without Boxing them
  • the underlying concrete type might be very long, tedious, or likely to change in irrelevant ways during refactors
    • this most often came up with iterator and future combinators
  • hiding the underlying concrete type prevents clients from accidentally relying on it

Since this technically has nothing to do with the current topic, if you’re still curious I’d recommend reading the blog post announcing its stabilization for the official elevator pitch, or for way more information there’s https://github.com/rust-lang/rust/issues/34511 and the various RFCs it links to.

9 Likes

For closures, that is impossible because their type cannot be syntactically expressed in the language.

Somewhat relatedly... is there a good definition of existential in the Rust context?

Not that I’m aware of, the term stems from type theory and is covered in https://wiki.haskell.org/Existential_type.

In principle, an existential is an Union Type, including all the values of it’s type parameters. Each value of such an Union Type belongs at least to one type in the type parameter list of this Union Type, hence the name existential.

From the implementation perspective, it is like Rust’s/(C’s) Union Type, which itself is a boxed value specifying the current typeid beside the data or a fat pointer in case of an unbounded Union Type which is, I think, the case for trait objects as we don’t know all the instances of a trait.

1 Like

In very simple terms (which are nonetheless sufficient for this usage):

  • existential quantification means "there exists a type (that satisfies the given bound) and I'm returning it to you"
  • universal quantification means "for all types (that satisfy the given bound), I will accept them"
6 Likes
  • dyn is chosen at run time and has runtime overhead. You have a lot of freedom in what underlying type is actually returned.

  • impl (in return position) is chosen at compile time and allows more optimized code. It’s just a syntax sugar for hardcoding a single type.

9 Likes

This is unfortunately wrong, and because of this pervasive confusion we should probably move away from using the words "existential" and "universal" to refer to impl Trait altogether.

That is, impl Trait is existential in argument position, as a parameter of that type can hold a value of any type that satisfies the constraint. This is isomorphic to the usual claim that impl Trait is universal in argument position- the function type and parameter are interchangeably universally quantified.

impl Trait in return position is really neither existential nor universal, at least not in the senses used above. If it were existential, the function would be able to return more than one type dynamically. If it were universal, the caller would be able to control the return type, like str::parse or Iterator::collect. It's probably more useful to think of it as a controlled form of type inference, along with its cousin existential type (new name pending).

6 Likes

dyn Trait is a concert type, and can be used in any places that you need a concert type (However, it is unsized, and in many places there is an implicit Sized bound, so not quite everywhere). Under the hook, it contains a vtable of a type implements Trait, and something that let the compiler refer to the type underneath.

impl Trait is an anonymous type. It is a type that you cannot name, which means any two impl Trait occurrences represents different types. A proved RFC proposed to allow using it in a type alias statements so it maybe aliased.

In argument position, impl Trait allows the caller pass on any single type that implements Trait. In the return position, impl Trait allows the callee returns any single type that implements Trait. In any case, this type cannot be referred to in any further contexts, and it is always the value creator to pick up a type.

3 Likes

Having long been unable to reach a personal level of understanding around the term existential sufficient to teach my coworkers, I followed @rpjohnst's comment here down a great thread of RFCs and discovered this great blog post, which doesn't specifically address the OP, but is very helpful to understand the impl Trait and existential concepts and how they might change:

@earthengine's reply does in awesome job crystallizing how I understand them to work in current stable rust.

Specific RFCs discussing impl Trait as a type alias as mentioned:

5 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.