I am struggling with the basics of object safety. If I have this code
struct S {
x: i32,
}
trait Trait: Sized {
fn f(&self) -> i32
where
Self: Sized;
}
fn object_safety_dynamic(x: Trait) {}
I receive
error[E0038]: the trait `Trait` cannot be made into an object
--> src/lib.rs:11:29
|
5 | trait Trait: Sized {
| ----- ----- ...because it requires `Self: Sized`
| |
| this trait cannot be made into an object...
...
11 | fn object_safety_dynamic(x: Trait) {}
| ^^^^^ the trait `Trait` cannot be made into an object
When I add or remove : Sized
as the supertrait or as f
's bound, I receive slightly different error messages.
Could someone explain:
Why does this particular example not work? The chapter Trait Objects states:
So what makes a method object-safe? Each method must require that
Self: Sized
Isn't that fulfilled?
What is the difference between Trait: Sized
and where Self: Sized
? (Well, yes, one inherits the trait and the other one is a parameter bound, but from Rust's trait object perspective?
What is the preferred change I had to make object_safety_dynamic
work?
I am using rustc 1.19.0-nightly (01951a61a 2017-05-20)
if it matters.
Addressing the comment on fixed sizes.
trait TraitB {
fn f(&self) -> i32
where
Self: Sized;
fn g<T>(&self, t: T) -> i32
where
Self: Sized;
}
Why does this particular example not work? The chapter Trait Objects states:
So what makes a method object-safe? Each method must require that
Self: Sized
Isn't that fulfilled?
This question really is: What is a trait object?
A trait object is an interface in the Object-Oriented paradigm:
The fact that the concrete type to which the operations is applied is unknown is specifically why one uses a trait object, as it allows manipulating a heterogeneous set of types in a uniform fashion down to the assembly level.
The fact the concrete type is unknown, however, means that the size of the memory area which contains the memory is also unknown; therefore a trait object can only be manipulated behind a reference or pointer such as &dyn TraitObject
, &mut dyn TraitObject
or Box<dyn TraitObject>
for example.
At the memory level, each of them is represented identically:
What is the difference between
Trait: Sized
and whereSelf: Sized
? (Well, yes, one inherits the trait the other one is a parameter bound, but from Rust's trait object perspective?)
There is no inheritance in Rust. In both cases those are bounds:
Trait: Sized
states that the trait itself can only be implemented for a type that already implements Sized
,fn method(&self) where Self: Sized
states that only types that implement Sized
can implement this method.Note: when implementing a trait, all methods must end up having a definition; the latter is therefore only really useful if a default implementation is provided for the method with the Self: Sized
bound, as is shown here.
What is the preferred change I had to make
object_safety_dynamic
work?
You have to take the trait object by reference or pointer. Whether you use a reference or pointer depends on whether you want to transfer ownership or not.
Making Trait
a supertype of Sized
doesn't help - in fact it is not permitted, as the error message says. Each implementation of Trait
will still have a different size, so your function object_safety_dynamic
cannot be compiled. Monomorphization cannot be used here because there is no generic parameter, so the compiled function must work for all implementations of Trait
.
However, references do have a fixed size, so making the argument into a reference will work:
trait Trait {
fn f(&self) -> i32;
}
fn object_safety_dynamic(x: &Trait) {}
A trait object is always a reference of some kind, e.g. a Box<T>
or &T
. This is precisely because the size of implementations of the trait will be different, while a reference type has a known, fixed size.
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