I have this code (playground):
trait NodeLike: Sized {}
fn main() {
let s: Box<NodeLike> = panic!();
}
Which does not compile:
error[E0038]: the trait `NodeLike` cannot be made into an object
--> src/main.rs:4:12
|
4 | let s: Box<NodeLike> = panic!();
| ^^^^^^^^^^^^^ the trait `NodeLike` cannot be made into an object
|
= note: the trait cannot require that `Self : Sized`
After all I read, I still don't understand why it does not compile and why it does without the Sized
constraint.
As far as I know:
Box<NodeLike>
is treated as Box<dyn NodeLike>
which uses dynamic dispatch for method calls.Box<NodeLike>
is sized anyways, regardless of its item type.Sized
marker on traits enforces implementing types to be sized.What does requiring that implementing types are Sized
have to do with not being able to have objects (with dynamic dispatch) of that trait?
A trait object is an opaque value of another type that implements a set of traits. The set of traits is made up of an object safe base trait plus any number of auto traits. Trait objects implement the base trait, its auto traits, and any supertraits of the base trait.
But trait objects differ from traditional objects in that we can't add data to a trait object. Trait objects aren't as generally useful as objects in other languages: their specific purpose is to allow abstraction across common behavior.
The Sized trait in Rust is an auto trait and a marker trait. Auto traits are traits that get automatically implemented for a type if it passes certain conditions. Marker traits are traits that mark a type as having a certain property.
A trait in Rust is a group of methods that are defined for a particular type. Traits are an abstract definition of shared behavior amongst different types. So, in a way, traits are to Rust what interfaces are to Java or abstract classes are to C++. A trait method is able to access other methods within that trait.
Having Self: ?Sized
on the trait type itself is a required property for a trait object, i.e. for 'object safety', even though you can have an impl
on a Self: ?Sized
trait with a Sized
type. Hence confusion.
It's a drawback that was decided upon in RFC 255 which deals with object safety (warning: obsolete Rust syntax).
It's a long read, but one of the alternatives was to determine 'object safety' by only analyzing the methods of the trait. It is admitted in the RFC that having this restriction will make some code that could have worked not to compile. ("This is a breaking change and forbids some safe code which is legal today.").
We can go around this if we lower the restriction only to the trait members function that actually need it, e.g. this compiles:
trait NodeLike {
fn method_foo(&self) -> Self
where
Self: Sized;
}
fn main() {
let s: Box<NodeLike> = panic!();
// Compiles!
}
However, we cannot call those Self: Sized
methods via a trait object, and this is a limitation that is explained elsewhere. Here, calling s.method_foo();
will break compilation.
Note that the Self: Sized
constraint limits compilation even if the method does not make use of Self
at all and could have been a callable trait object method otherwise.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With