What is the difference between impl Trait and dyn Trait?


#1

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


Trait objects: Blocking entire traits vs blocking members
#3

Isn’t

fn fun(s:Something)->impl Iterator

the same as

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

?


#4

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.


#5

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.


#6

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.


#7

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


#8

The crucial difference is who chooses the return type. The caller or the callee?

In fn fun<T: Iterator>(s: Something) -> T, the caller chooses the concrete return type. For example, they can even be explicit and call it like this: let iter = fun::<MyIterator>(something). Here, the caller chose the concrete type MyIterator.

In fn fun(s: Something) -> impl Iterator, the caller cannot choose the return type and doesn’t even know what the concrete type it is - they only know it implements Iterator. For example, the caller might do let iter = fun(something) and you can see it’s impossible to choose the return type. Rather, the callee (the invoked function) chose the return type for us.


#9

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


#10

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.


#11

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”

#12
  • 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.


#13

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).


#14

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.


#15

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: