From 7907fa8ec4cd4e7b60687e3f06b12e2b8fff6a04 Mon Sep 17 00:00:00 2001 From: Graydon Hoare Date: Mon, 29 Nov 2021 22:10:49 -0800 Subject: [PATCH] Clarify and tidy up explanation of E0038 --- .../src/error_codes/E0038.md | 97 +++++++++++++------ 1 file changed, 65 insertions(+), 32 deletions(-) diff --git a/compiler/rustc_error_codes/src/error_codes/E0038.md b/compiler/rustc_error_codes/src/error_codes/E0038.md index 019d54b6202ed..ca2eaa54057fa 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0038.md +++ b/compiler/rustc_error_codes/src/error_codes/E0038.md @@ -1,34 +1,64 @@ -Trait objects like `Box` can only be constructed when certain -requirements are satisfied by the trait in question. - -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`, 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. - -Attempting to create a trait object for a non object-safe trait will trigger -this error. - -There are various rules: - -### The trait cannot require `Self: Sized` - -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: +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`. + +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. + +Two general aspects of trait object types give rise to the restrictions: + + 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`. 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. + + 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. + +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(b: &T) { +} +fn dynamic_bar(a: &dyn Trait) { + static_foo(a) } ``` -We cannot create an object of type `Box` 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) { +fn call_foo(x: Box) { let y = x.foo(); // What type is y? // ... } @@ -76,7 +106,8 @@ fn call_foo(x: Box) { 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` which is itself sized (assuming you `impl Trait for Box`). +`Box` which is itself sized (assuming you `impl Trait for Box`). ``` 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(&self, on: T) {} } # impl Trait for bool { fn foo(&self, on: T) {} } # // etc. -fn call_foo(thing: Box) { +fn call_foo(thing: Box) { 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`). If the +trait object (e.g., if `T: OtherTrait`, use `on: Box`). 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: