Reputation: 751
Rust doesn't allow me to write code like this:
trait MyTrait {
fn foo();
}
impl MyTrait for u8 {
fn foo() {}
}
fn main() {
let v = 1u8;
let o = &v as &dyn MyTrait;
}
Adding where Self: Sized
to fn foo()
as the compiler suggests makes it work:
trait MyTrait {
fn foo() where Self: Sized;
}
My first question is, why do I need a constraint for something that I'm not using?
Furthermore, if I understand it correctly, adding where Self: Sized
makes a non-dispatchable function, which means I can't call o.foo()
. (This doesn't quite apply to a non-method function, but just to emphasize its unappealing side effect.) I would need other solutions to make a dispatchable function. Another solution is to add &self
as the first parameter, turning the function into a method, but I can't find an intuitive explanation why a method works here but a function doesn't. This raises a similar question: Why must I add a parameter that I'm not using (just to make the code compile)?
Another example that is not working is:
trait MyTrait {
fn foo(&self) -> &Self;
}
impl MyTrait for u8 {
fn foo(&self) -> &Self {
self
}
}
fn main() {
let v = 1u8;
let o = &v as &dyn MyTrait;
}
My second question is, why can't we return &Self
(note: not Self
) from a trait object?
I'm not seeking possible fixes (compiler has them). Instead I'm asking for the reasons behind these design decisions.
Upvotes: 5
Views: 404
Reputation: 125
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.
Upvotes: 2