Rust's type-erasing dyn Trait offers a way to treat different implementors of a trait in a homogenous fashion while remaining strictly and statically (i.e. compile-time) typed. For example: if you want a Vec of values which implement your trait, but they might not all be the same base type, you need type erasure so that you can create a Vec<Box<dyn Trait>> or similar.
dyn Trait is also useful in some situations where generics are undesirable, or to type erase unnameable types such as closures into something you need to name (such as a field type, an associated type, or a trait method return type).
There is a lot to know about when and how dyn Trait works or does not, and how this ties together with generics, lifetimes, and Rust's type system more generally. It is therefore not uncommon to get somewhat confused about dyn Trait when learning Rust.
In this section we take a look at what dyn Trait is and is not, the limitations around using it, how it relates to generics and opaque types, and more.
dyn Trait has a lot of special functionality and corner-cases, so this ended up being quite long. The guide is really also part reference material (due to the high number of corner-cases covered) and part cookbook (as some parts of working with dyn Trait are just best illustrated by example).
Feedback is welcome here or on GitHub (though I'm more attentive here).
Re dyn Trait coercion. There are actually two types of coercions: The ones that only happen in CoerceUnsized positions, and this not behind any double-indirection, and the ones that are also true subtyping and supported behind deep nesting, given the right variance.
dyn Trait + 'a to dyn Trait + 'b coercion is of the second type, so that it works for
types like Vec<Box<dyn Trait + '_>> because of covariance, but also
&mut (dyn Trait + '_) because that position supports unsized coercions, and despite the invariance of the type parameter of &mut T.
This means your remark on "the reflective case" that "this coercion can still not happen in a nested context" is wrong.
The same applies to the dyn for<'a> Trait<'a> to dyn Trait<'b> coercion that you don't even mention at all (or I didn't see it).
This is an interesting example of subtyping between two types T: 'static – like Box<dyn for<'a> Trait<'a>> to Box<dyn Trait<'static>> – which demonstrates that T: 'static is insufficient to make sure you don't need to think about variance (which may be relevant for designing unsafe code; e. g. these two types will not have the same TypeId, so never cache the TypeId of a covariant parameter even it it's bound by 'static).
You mean it's both basically, right? The first can happen in places of invariance, but not nested; the second can happen nested, but not in places of invariance. Anyway, I should have an example pointing this out. ...Probably you haven't reached a later section about variance yet, I'll think about how to make the earlier section less counterfactual or counterfactual-seeming.
I don't really cover higher-ranked types at all,[1] or Fn and friends (a very common target for dyn). It would be another sizable addition -- which may happen, but if it does, it will probably be a new section about higher-ranked types generally, function types and fn pointers, closure types, and the Fn traits.
Probably I should figure out a place in this section to at least briefly go over higher-ranked dyn Trait types.
a brief fn pointer example in the dyn Any section ↩︎
I see. Indeed I didn't read the later chapter yet. I thing a forward link might be enough. Something like "Despite that, this coercion can still not happen in a nested context, however as we discuss later variance rules can still allow for similar coercions." (with appropriate links added).
Also, is this heading supposed to say "reflexive"?
If your type has no lifetime parameter, or if there is no bound between the type parameter and the lifetime parameter, the default for elided dyn Trait lifetimes will be 'static, like it is for Box<T>. This is true even if there is an impliedT: 'a bound.
That last sentence is an impressive observation and something I didn't know. Do you know if this is a deliberate design decision?
I'm wondering if saying that trait object types are "statically known" might be confusing.
The compiler knows about the concrete type at compile time to assure that it does implement the trait, but saying it's statically known seems confusing to me since it will be looking up the concrete type in the vtable at runtime, right? I'd think the reason it allows you to store trait objects of different concrete types in the same Vec is that the concrete type isn't statically known (in that code location).
Same for me. On my phone, the website only displays properly on Firefox (among the two browsers I tested), while other mdbook-generated pages work fine.
I think the point of that section is that, from the perspective of the type system, dyn Traitis a single concrete type— There's plenty of compiler magic involved to create one, but once you have one it behaves exactly the same as any other !Sized type.
If the type system believed it was a single concrete type, wouldn't we be able to call non-trait methods on the type? It seems very confusing to word it like this since trait objects are a form of polymorphism.
You can define and call inherent methods on dyn Trait just fine, with the only caveat being that they can't have the same name as the trait's methods. The most common example of this is probably these inherent methods of dyn Any
The point is, like @2e71828 said, that dyn Trait + 'a is a distinct, concrete, statically known type and not a dynamic type. The erased base type isn't statically known.
I'll see if I can fit in an example of implementing an inherant method on a dyn Trait and some other forward links like implementing other traits on dyn Trait. If you have other ideas on how I can make the distinction clear, please let me know!