BTW, what is the diffference between dyn Trait and impl Trait when used as types?
Isn’t
fn fun(s:Something)->impl Iterator
the same as
fn fun<T: Iterator> (s:Something)->T
?
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
.
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.
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
Box
ing them
- in particular, this makes it possible to write functions that return closures without
- 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.
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.
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"
-
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.
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).
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.
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:
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.