These two functions, one trait and one free, seem to be similar but calling one of them (the trait function) is allowed while calling the other isn't:
trait A {
fn foo(&self) {
bar(self); // 1. Error: `Self` does not have a constant size known at compile-time
A::bar(self); // 2. This works
}
fn bar(&self) {}
}
fn bar(_a: &A) {}
(Playground link for above)
I would have thought in both cases I'm accessing via a pointer whose size is known at compile time, so what is the difference and the explanation for this behaviour ?
(Rust 1.19 stable)
Because the two want totally different things.
The type of self
inside of A::foo
is &Self
, not &A
. That is, it's a pointer to the implementing type, not a pointer to a trait object. Calling A::bar
is fine because it's just passing said pointer along, no extra work necessary.
Calling ::bar
is a whole different cooking vessel of marine life.
The problem boils down to how the compiler represents things. Let's first consider the case where Self
is a Sized
type like i32
. This means self
is a &i32
. Recall that &A
is a trait object, and thus effectively has the following layout:
struct ATraitObject {
ptr: *const (),
vtable: *const AVtable,
}
ATraitObject.ptr
needs to point to the actual value itself, and ATraitObject.vtable
to the implementation of the trait for that type. The compiler can fill this out with ptr
being the existing self
pointer, and vtable
is filled with a pointer to wherever the impl A for i32
vtable lives in memory. With that, it can call ::bar
.
Now consider the case where Self
is not Sized
, like str
. This means self
is a "fat" pointer, and contains two pointer's worth of information: a pointer to the underlying data and the length of the string. When the compiler goes to create the ATraitObject
, it can set ptr
to self.as_ptr()
, and it can set vtable
... but there's nowhere for it to store the string's length!
All non-Sized
types have this problem, as they all have extra information carried around in their pointers. Incidentally, this includes trait objects like &A
, meaning that you also can't turn a trait object into another trait object, since you'd now need two vtables.
That's the problem: the compiler simply has no way of turning &Self
where Self: !Sized
into an &A
. There's too much information contained in self
and not enough space to store it in an &A
.
To get the method to compile, add a where Self: Sized
clause to the method definition. This guarantees to the compiler that the method will never be called on non-Sized
types, and thus it's free to assume the conversion from &Self
to &A
is always possible.
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