I have the following code:
trait Bar {
fn baz(&self, arg: impl AsRef<str>)
where
Self: Sized;
}
struct Foo;
impl Bar for Foo {
fn baz(&self, arg: impl AsRef<str>) {}
}
fn main() {
let boxed: Box<dyn Bar> = Box::new(Foo);
boxed.baz();
}
playground
Which results in this error:
error: the `baz` method cannot be invoked on a trait object
--> src/main.rs:15:11
|
15 | boxed.baz();
| ^^^
Why is this not possible? It works when I remove the Self: Sized
bound, but then I can't use generics which make the function more comfortable for the caller.
This is not a duplicate of Why does a generic method inside a trait require trait object to be sized? which asks why you can't call baz
from a trait object. I'm not asking why the bound is required; this has already been discussed.
The Sized trait in Rust is an auto trait and a marker trait. Auto traits are traits that get automatically implemented for a type if it passes certain conditions. Marker traits are traits that mark a type as having a certain property.
A trait object is an opaque value of another type that implements a set of traits. The set of traits is made up of an object safe base trait plus any number of auto traits. Trait objects implement the base trait, its auto traits, and any supertraits of the base trait.
Object-safe traits are traits with methods that follow these two rules: the return type is not Self. there are no generic types parameters.
dyn is a prefix of a trait object's type. The dyn keyword is used to highlight that calls to methods on the associated Trait are dynamically dispatched. To use the trait this way, it must be 'object safe'. Unlike generic parameters or impl Trait , the compiler does not know the concrete type that is being passed.
Because Rust's generics system works through monomorphization.
In Java, for example, type parameters in a generic function turn into variables of type Object, and are casted as necessary. Generics in languages like this simply serves as a tool to help verify the correctness of types within code.
Languages such as Rust and C++ use monomorphization for generics. For each combination of type parameters a generic function is invoked with, specialized machine code is generated which runs that function with those combinations of type parameters. The function is monomorphized. This allows data to be stored in place, eliminates the cost of casting, and allows the generic code to call "static" functions on that type paramameter.
So why can't you do that on a trait object?
Trait objects in many languages, including Rust, are implemented using a vtable. When you have some type of pointer to a trait object (raw, reference, Box, reference counter, etc.), it contains two pointers: the pointer to the data, and a pointer to a vtable entry. The vtable entry is a collection of function pointers, stored in an immutable memory region, which point to the implementation of that trait's methods. Thus, when you call a method on a trait object, it looks up the function pointer of the implementation in the vtable, and then makes an indirect jump to that pointer.
Unfortunately, the Rust compiler cannot monomorphize functions, if it does not know at compile time the code that implements the function, which is the case when you call a method on a trait object. For that reason, you cannot call a generic function (well, generic over types) on a trait object.
-Edit-
It sounds like you're asking why the : Sized
restriction is necessary.
: Sized
makes it so that the trait cannot be used as a trait object. I suppose there could be a couple of alternatives. Rust could implicitly make any trait with generic functions not object safe. Rust could also implicitly prevent generic functions from being called on trait objects.
However, Rust tries to be explicit with what the compiler is doing, which these implicit approaches would go against. Wouldn't it be confusing, anyways, for a beginner to try and call a generic function on a trait object and have it fail to compile?
Instead, Rust lets you explicitly make the entire trait not object safe
trait Foo: Sized {
Or explicitly make certain functions only available with static dispatch
fn foo<T>() where Self: Sized {
The bound makes the method not object safe. Traits that are not object safe cannot be used as types.
Methods that take
Self
as an argument, returnSelf
or otherwise requireSelf: Sized
are not Object safe. That's because methods on a trait object are called via dynamic dispatch and the size of the trait implementation cannot be known at compile time. -- Peter Hall
Citing the official docs:
Only traits that are object-safe can be made into trait objects. A trait is object-safe if both of these are true:
- the trait does not require that
Self: Sized
- all of its methods are object-safe
So what makes a method object-safe? Each method must require that
Self: Sized
or all of the following:
- must not have any type parameters
- must not use
Self
See also:
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