Clarify and tidy up explanation of E0038
This commit is contained in:
parent
94bec90702
commit
7907fa8ec4
@ -1,34 +1,64 @@
|
||||
Trait objects like `Box<Trait>` can only be constructed when certain
|
||||
requirements are satisfied by the trait in question.
|
||||
For any given trait `Trait` there may be a related _type_ called the _trait
|
||||
object type_ which is typically written as `dyn Trait`. In earlier editions of
|
||||
Rust, trait object types were written as plain `Trait` (just the name of the
|
||||
trait, written in type positions) but this was a bit too confusing, so we now
|
||||
write `dyn Trait`.
|
||||
|
||||
Trait objects are a form of dynamic dispatch and use a dynamically sized type
|
||||
for the inner type. So, for a given trait `Trait`, when `Trait` is treated as a
|
||||
type, as in `Box<Trait>`, the inner type is 'unsized'. In such cases the boxed
|
||||
pointer is a 'fat pointer' that contains an extra pointer to a table of methods
|
||||
(among other things) for dynamic dispatch. This design mandates some
|
||||
restrictions on the types of traits that are allowed to be used in trait
|
||||
objects, which are collectively termed as 'object safety' rules.
|
||||
Some traits are not allowed to be used as trait object types. The traits that
|
||||
are allowed to be used as trait object types are called "object-safe" traits.
|
||||
Attempting to use a trait object type for a trait that is not object-safe will
|
||||
trigger error E0038.
|
||||
|
||||
Attempting to create a trait object for a non object-safe trait will trigger
|
||||
this error.
|
||||
Two general aspects of trait object types give rise to the restrictions:
|
||||
|
||||
There are various rules:
|
||||
1. Trait object types are dynamically sized types (DSTs), and trait objects of
|
||||
these types can only be accessed through pointers, such as `&dyn Trait` or
|
||||
`Box<dyn Trait>`. The size of such a pointer is known, but the size of the
|
||||
`dyn Trait` object pointed-to by the pointer is _opaque_ to code working
|
||||
with it, and different tait objects with the same trait object type may
|
||||
have different sizes.
|
||||
|
||||
### The trait cannot require `Self: Sized`
|
||||
2. The pointer used to access a trait object is paired with an extra pointer
|
||||
to a "virtual method table" or "vtable", which is used to implement dynamic
|
||||
dispatch to the object's implementations of the trait's methods. There is a
|
||||
single such vtable for each trait implementation, but different trait
|
||||
objects with the same trait object type may point to vtables from different
|
||||
implementations.
|
||||
|
||||
When `Trait` is treated as a type, the type does not implement the special
|
||||
`Sized` trait, because the type does not have a known size at compile time and
|
||||
can only be accessed behind a pointer. Thus, if we have a trait like the
|
||||
following:
|
||||
The specific conditions that violate object-safety follow, most of which relate
|
||||
to missing size information and vtable polymorphism arising from these aspects.
|
||||
|
||||
### The trait requires `Self: Sized`
|
||||
|
||||
Traits that are declared as `Trait: Sized` or which otherwise inherit a
|
||||
constraint of `Self:Sized` are not object-safe.
|
||||
|
||||
The reasoning behind this is somewhat subtle. It derives from the fact that Rust
|
||||
requires (and defines) that every trait object type `dyn Trait` automatically
|
||||
implements `Trait`. Rust does this to simplify error reporting and ease
|
||||
interoperation between static and dynamic polymorphism. For example, this code
|
||||
works:
|
||||
|
||||
```
|
||||
trait Foo where Self: Sized {
|
||||
trait Trait {
|
||||
}
|
||||
|
||||
fn static_foo<T:Trait + ?Sized>(b: &T) {
|
||||
}
|
||||
|
||||
fn dynamic_bar(a: &dyn Trait) {
|
||||
static_foo(a)
|
||||
}
|
||||
```
|
||||
|
||||
We cannot create an object of type `Box<Foo>` or `&Foo` since in this case
|
||||
`Self` would not be `Sized`.
|
||||
This code works because `dyn Trait`, if it exists, always implements `Trait`.
|
||||
|
||||
However as we know, any `dyn Trait` is also unsized, and so it can never
|
||||
implement a sized trait like `Trait:Sized`. So, rather than allow an exception
|
||||
to the rule that `dyn Trait` always implements `Trait`, Rust chooses to prohibit
|
||||
such a `dyn Trait` from existing at all.
|
||||
|
||||
Only unsized traits are considered object-safe.
|
||||
|
||||
Generally, `Self: Sized` is used to indicate that the trait should not be used
|
||||
as a trait object. If the trait comes from your own crate, consider removing
|
||||
@ -67,7 +97,7 @@ trait Trait {
|
||||
fn foo(&self) -> Self;
|
||||
}
|
||||
|
||||
fn call_foo(x: Box<Trait>) {
|
||||
fn call_foo(x: Box<dyn Trait>) {
|
||||
let y = x.foo(); // What type is y?
|
||||
// ...
|
||||
}
|
||||
@ -76,7 +106,8 @@ fn call_foo(x: Box<Trait>) {
|
||||
If only some methods aren't object-safe, you can add a `where Self: Sized` bound
|
||||
on them to mark them as explicitly unavailable to trait objects. The
|
||||
functionality will still be available to all other implementers, including
|
||||
`Box<Trait>` which is itself sized (assuming you `impl Trait for Box<Trait>`).
|
||||
`Box<dyn Trait>` which is itself sized (assuming you `impl Trait for Box<dyn
|
||||
Trait>`).
|
||||
|
||||
```
|
||||
trait Trait {
|
||||
@ -115,7 +146,9 @@ impl Trait for u8 {
|
||||
```
|
||||
|
||||
At compile time each implementation of `Trait` will produce a table containing
|
||||
the various methods (and other items) related to the implementation.
|
||||
the various methods (and other items) related to the implementation, which will
|
||||
be used as the virtual method table for a `dyn Trait` object derived from that
|
||||
implementation.
|
||||
|
||||
This works fine, but when the method gains generic parameters, we can have a
|
||||
problem.
|
||||
@ -174,7 +207,7 @@ Now, if we have the following code:
|
||||
# impl Trait for u8 { fn foo<T>(&self, on: T) {} }
|
||||
# impl Trait for bool { fn foo<T>(&self, on: T) {} }
|
||||
# // etc.
|
||||
fn call_foo(thing: Box<Trait>) {
|
||||
fn call_foo(thing: Box<dyn Trait>) {
|
||||
thing.foo(true); // this could be any one of the 8 types above
|
||||
thing.foo(1);
|
||||
thing.foo("hello");
|
||||
@ -200,7 +233,7 @@ trait Trait {
|
||||
```
|
||||
|
||||
If this is not an option, consider replacing the type parameter with another
|
||||
trait object (e.g., if `T: OtherTrait`, use `on: Box<OtherTrait>`). If the
|
||||
trait object (e.g., if `T: OtherTrait`, use `on: Box<dyn OtherTrait>`). If the
|
||||
number of types you intend to feed to this method is limited, consider manually
|
||||
listing out the methods of different types.
|
||||
|
||||
@ -226,7 +259,7 @@ trait Foo {
|
||||
}
|
||||
```
|
||||
|
||||
### The trait cannot contain associated constants
|
||||
### Trait contains associated constants
|
||||
|
||||
Just like static functions, associated constants aren't stored on the method
|
||||
table. If the trait or any subtrait contain an associated constant, they cannot
|
||||
@ -248,7 +281,7 @@ trait Foo {
|
||||
}
|
||||
```
|
||||
|
||||
### The trait cannot use `Self` as a type parameter in the supertrait listing
|
||||
### Trait uses `Self` as a type parameter in the supertrait listing
|
||||
|
||||
This is similar to the second sub-error, but subtler. It happens in situations
|
||||
like the following:
|
||||
|
Loading…
x
Reference in New Issue
Block a user