What makes a trait "object safe", and what makes it not?
The exact rules as defined in the reference:
- All supertraits must also be object safe.
Sized
must not be a supertrait. In other words, it must not require Self: Sized
.
- It must not have any associated constants.
- It must not have any associated types with generics.
- All associated functions must either be dispatchable from a trait object or be explicitly non-dispatchable:
- Dispatchable functions require:
- Not have any type parameters (although lifetime parameters are allowed),
- Be a method that does not use
Self
except in the type of the receiver.
- Have a receiver with one of the following types:
&Self
(i.e. &self
)
&mut Self
(i.e &mut self
)
Box<Self>
Rc<Self>
Arc<Self>
Pin<P>
where P is one of the types above
- Does not have a
where Self: Sized
bound (receiver type of Self
(i.e. self
) implies this).
- Explicitly non-dispatchable functions require:
- Have a
where Self: Sized
bound (receiver type of Self
(i.e. self
) implies this).
Generally, a trait is object-safe if we can create a vtable for it (not all rules are strictly necessary, but most of them are).
Here's a summary of the reasons for the rules:
- Obvious.
- Not strictly necessary, this is a way to explicitly opt-out for object safety. Sometimes it is wanted.
- Not strictly necessary, a question of syntax and need.
- It was just unusable: to make a
dyn Trait
for a trait with associated type, you need to specify the type of the associated type (dyn Trait<Assoc = Ty>
). This is because the compiler may need to know the concrete type when the associated type is used, e.g. in methods. There is just no way to specify the type for a generic associated type, and the semantics are also somewhat unclear.
-
-
- As generic functions are monorphized, creating a new function for each instantiation, the vtable would need to contain entries for each possible instantiation, but there are infinitely such many.
- It cannot return
Self
, because to return a type we need to save space for it on the stack, but we don't know how big Self
is because we don't know its concrete type. It cannot take Self
as a parameter, because the semantics would be unclear: what type will the caller provide? If dyn Trait
, it could provide a type different than Self
, but the function is expecting the same type! If Self
, we don't know what it is in the caller!
- The compiler needs to know the layout of the receiver. For normal methods, basically every type could be used as a receiver, and the question what to allow is a question of what we want, because the receiver is just a normal parameter with syntax sugar. But for methods dispatched via
dyn Trait
, The receiver needs to contain a vtable, so we know what method to use. Also, when we call the method, we call it with wide pointer (data pointer + vtable). But the method itself expects a thin pointer (only the data pointer), because it knows the concrete type. The compiler needs to be able to convert between the two. The current solution is just to use a fixed set of possible types, known to the compiler (more precisely, types that implement std::ops::DispatchFromDyn
, and this trait has restrictions on what types can implement it). This may be changed in the future to allow the user to extend this list.
- Not necessary, just a way to opt-out for object safety for a specific method.
- A way to opt-out for object safety for a specific method, leaving the overall trait object-safe but with it being undispatchable via
dyn Trait
.
Can we inherit an "unsized" trait and make our child trait "size/object safe" at the same time?
If by "unsized" you mean "non object-safe", then the answer is no. As said above, one of the rules for object safety is that all supertraits are object safe.
What's the usage of a "not object safe" trait?
An object safe trait can be used for dynamic polymorphism (dyn Trait
), in addition to static polymorphism (generics). Non-object-safe traits can only be used for static polymorphism. Most of our polymorphism in Rust is static, so many times this does not matter.
Why we cannot make a Box<dyn trait>
when the trait is "not object safe"? Given the fact that we are manipulating objects on heap, why does the compiler still not let me go?
The very much definition of object safe trait is "allowed to be used in dyn Trait
". If the trait is not object-safe, we cannot create the vtable part for Box<dyn Trait>
.
So how do I fix that?
In general, you cannot. If a trait is not object-safe, it cannot be made object safe.
However, sometimes you can. For example, for Clone
, see How to clone a struct storing a boxed trait object?. Ord
is more complicated, but sometimes it is also possible, see How to test for equality between trait objects?.