I try to explain it without considering very deep details of rust compiler. So warning, this answer may not be 100% correct, but you can get at least some intuition of what's going on.
Step1 : Let's check compiler's advice.
for a trait to be "object safe" it needs to allow building a vtable to
allow the call to be resolvable dynamically; for more information
visit
https://doc.rust-lang.org/reference/items/traits.html#object-safety
Simply speaking, if you want to make trait object, such trait must be object safe. To make trait object safe, there are several conditions but focus on this sentence at first.
All associated functions must either be dispatchable from a trait object or be explicitly non-dispatchable:
Step2 : what "dispatchable" means?
Consider following code.
fn bar<T:MyTrait>(_ : T ){
T::foo();
}
This works perfectly fine. However, let's imagine that you try to do similar thing with trait object.
fn bar_dyn(v : &dyn MyTrait ){
//...
}
Hmm... ok. How can we call foo
? There is no way to call foo
in this bar_dyn
. But you may want to use foo
because you implemented it. It's very akward situation. So rust compiler try to prevent this. That's why associated function of object safe trait must be "dispatchable". You can check if foo
satisfy every requirement to be dispatchable, you now can call foo
in above example.
Step3 : Why adding where Self: Sized
resolve the problem?
But in some cases, you still want to use trait object even though you cannot use function foo
. So, there's trick. You can tell compiler like this. "Hey. I know that I cannot use function foo
via trait object and it's intentional."
By adding where Self: Sized
, you restrict that the type or object which call foo
must have known size at compilation time. However, it's clear that size of trait object cannot be known at compilation time because basically any type can implement MyTrait
. Now look at the below.
trait MyTrait {
fn foo() where Self:Sized;
fn goo(&self);
}
impl MyTrait for u8{
fn foo() {}
fn goo(&self) {}
}
fn bar_dyn(v : &dyn MyTrait ){
v.goo();
}
It works! Because by adding where Self: Sized
, you tell the compiler that you promise to do nothing about foo
via trait object.
Step4: Why can't we return &Self from a trait object?
If we allow it, very dangeruos thing happens.
trait MyTrait {
fn foo(&self)->&Self;
}
impl MyTrait for u8 {
fn foo(&self)->&Self{self}
}
impl MyTrait for String {
fn foo(&self)->&Self{self}
}
fn baz<'s, T:MyTrait+?Sized>( a: &'s T,b: &'s T)->(&'s T,&'s T){
(a.foo(),b.foo())
}
fn main() {
let v1 = 1u8;
let v2 = String::from("hello");
let o1 = &v1 as &dyn MyTrait;
let o2 = &v2 as &dyn MyTrait;
let (r1,r2) = baz(o1,o2);
}
This code is not working because we cannot make trait obejct of MyTrait
. However, assume we can, and let's see what happens.
r1
and r2
is ensured to have same type by baz
. And o1
and o2
have same type which implement trait MyTrait
. (Every trait object implements correspodning trait) So we can call baz(o1,o2)
. And what? Now r1
has type u8
and r2
has type String
. It yields huge error of rust type checker.