In the code below it is not possible to obtain a reference to a trait object from a reference to a dynamically-sized type implementing the same trait. Why is this the case? What exactly is the difference between &dyn Trait
and &(?Sized + Trait)
if I can use both to call trait methods?
A type implementing FooTraitContainerTrait
might e.g. have type Contained = dyn FooTrait
or type Contained = T
where T
is a concrete type that implements FooTrait
. In both cases it's trivial to obtain a &dyn FooTrait
. I can't think of another case where this wouldn't work. Why isn't this possible in the generic case of FooTraitContainerTrait
?
trait FooTrait {
fn foo(&self) -> f64;
}
///
trait FooTraitContainerTrait {
type Contained: ?Sized + FooTrait;
fn get_ref(&self) -> &Self::Contained;
}
///
fn foo_dyn(dyn_some_foo: &dyn FooTrait) -> f64 {
dyn_some_foo.foo()
}
fn foo_generic<T: ?Sized + FooTrait>(some_foo: &T) -> f64 {
some_foo.foo()
}
///
fn foo_on_container<C: FooTraitContainerTrait>(containing_a_foo: &C) -> f64 {
let some_foo = containing_a_foo.get_ref();
// Following line doesn't work:
//foo_dyn(some_foo)
// Following line works:
//some_foo.foo()
// As does this:
foo_generic(some_foo)
}
Uncommenting the foo_dyn(some_foo)
line results in the compiler error
error[E0277]: the size for values of type `<C as FooTraitContainerTrait>::Contained` cannot be known at compilation time
--> src/main.rs:27:22
|
27 | foo_dyn(contained)
| ^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `std::marker::Sized` is not implemented for `<C as FooTraitContainerTrait>::Contained`
= note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
= help: consider adding a `where <C as FooTraitContainerTrait>::Contained: std::marker::Sized` bound
= note: required for the cast to the object type `dyn FooTrait`
This problem can be reduced to the following simple example (thanks to turbulencetoo):
trait Foo {}
fn make_dyn<T: Foo + ?Sized>(arg: &T) -> &dyn Foo {
arg
}
At first glance, it really looks like this should compile, as you observed:
T
is Sized
, the compiler knows statically what vtable it should use to create the trait object;T
is dyn Foo
, the vtable pointer is part of the reference and can just be copied to the output.But there's a third possibility that throws a wrench in the works:
T
is some unsized type that is not dyn Foo
, even though the trait is object safe, there is no vtable for impl Foo for T
.The reason there is no vtable is because the vtable for a concrete type assumes that self
pointers are thin pointers. When you call a method on a dyn Trait
object, the vtable pointer is used to look up a function pointer, and only the data pointer is passed to the function.
However, suppose you implement a(n object-safe) trait for an unsized type:
trait Bar {}
trait Foo {
fn foo(&self);
}
impl Foo for dyn Bar {
fn foo(&self) {/* self is a fat pointer here */}
}
If there were a vtable for this impl
, it would have to accept fat pointers, because the impl
may use methods of Bar
which are dynamically dispatched on self
.
This causes two problems:
Bar
vtable pointer in a &dyn Foo
object, which is only two pointers in size (the data pointer and the Foo
vtable pointer).Therefore, even though dyn Bar
implements Foo
, it is not possible to turn a &dyn Bar
into a &dyn Foo
.
Although slices (the other kind of unsized type) are not implemented using vtables, pointers to them are still fat, so the same limitation applies to impl Foo for [i32]
.
In some cases, you can use CoerceUnsized
(only on nightly as of Rust 1.36) to express bounds like "must be coercible to &dyn FooTrait
". Unfortunately, I don't see how to apply this in your case.
str
) that cannot be coerced to a reference to a trait object.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