@@ -102,49 +102,88 @@ reason.
102102Rust provides dynamic dispatch through a feature called 'trait objects.' Trait
103103objects, like ` &Foo ` or ` Box<Foo> ` , are normal values that store a value of
104104* any* type that implements the given trait, where the precise type can only be
105- known at runtime. The methods of the trait can be called on a trait object via
106- a special record of function pointers (created and managed by the compiler).
105+ known at runtime.
107106
108- A function that takes a trait object is not specialized to each of the types
109- that implements ` Foo ` : only one copy is generated, often (but not always)
110- resulting in less code bloat. However, this comes at the cost of requiring
111- slower virtual function calls, and effectively inhibiting any chance of
112- inlining and related optimisations from occurring.
107+ A trait object can be obtained from a pointer to a concrete type that
108+ implements the trait by * casting* it (e.g. ` &x as &Foo ` ) or * coercing* it
109+ (e.g. using ` &x ` as an argument to a function that takes ` &Foo ` ).
113110
114- Trait objects are both simple and complicated: their core representation and
115- layout is quite straight-forward , but there are some curly error messages and
116- surprising behaviors to discover .
111+ These trait object coercions and casts also work for pointers like ` &mut T ` to
112+ ` &mut Foo ` and ` Box<T> ` to ` Box<Foo> ` , but that's all at the moment. Coercions
113+ and casts are identical .
117114
118- ### Obtaining a trait object
115+ This operation can be seen as "erasing" the compiler's knowledge about the
116+ specific type of the pointer, and hence trait objects are sometimes referred to
117+ as "type erasure".
119118
120- There's two similar ways to get a trait object value: casts and coercions. If
121- ` T ` is a type that implements a trait ` Foo ` (e.g. ` u8 ` for the ` Foo ` above),
122- then the two ways to get a ` Foo ` trait object out of a pointer to ` T ` look
123- like:
119+ Coming back to the example above, we can use the same trait to perform dynamic
120+ dispatch with trait objects by casting:
124121
125- ``` {rust,ignore}
126- let ref_to_t: &T = ...;
122+ ``` rust
123+ # trait Foo { fn method (& self ) -> String ; }
124+ # impl Foo for u8 { fn method (& self ) -> String { format! (" u8: {}" , * self ) } }
125+ # impl Foo for String { fn method (& self ) -> String { format! (" string: {}" , * self ) } }
127126
128- // `as` keyword for casting
129- let cast = ref_to_t as &Foo;
127+ fn do_something (x : & Foo ) {
128+ x . method ();
129+ }
130130
131- // using a `&T` in a place that has a known type of `&Foo` will implicitly coerce:
132- let coerce: &Foo = ref_to_t;
131+ fn main () {
132+ let x = 5u8 ;
133+ do_something (& x as & Foo );
134+ }
135+ ```
133136
134- fn also_coerce(_unused: &Foo) {}
135- also_coerce(ref_to_t);
137+ or by coercing:
138+
139+ ``` rust
140+ # trait Foo { fn method (& self ) -> String ; }
141+ # impl Foo for u8 { fn method (& self ) -> String { format! (" u8: {}" , * self ) } }
142+ # impl Foo for String { fn method (& self ) -> String { format! (" string: {}" , * self ) } }
143+
144+ fn do_something (x : & Foo ) {
145+ x . method ();
146+ }
147+
148+ fn main () {
149+ let x = " Hello" . to_string ();
150+ do_something (& x );
151+ }
136152```
137153
138- These trait object coercions and casts also work for pointers like ` &mut T ` to
139- ` &mut Foo ` and ` Box<T> ` to ` Box<Foo> ` , but that's all at the moment. Coercions
140- and casts are identical.
154+ A function that takes a trait object is not specialized to each of the types
155+ that implements ` Foo ` : only one copy is generated, often (but not always)
156+ resulting in less code bloat. However, this comes at the cost of requiring
157+ slower virtual function calls, and effectively inhibiting any chance of
158+ inlining and related optimisations from occurring.
141159
142- This operation can be seen as "erasing" the compiler's knowledge about the
143- specific type of the pointer, and hence trait objects are sometimes referred to
144- as "type erasure".
160+ ### Why pointers?
161+
162+ Rust does not put things behind a pointer by default, unlike many managed
163+ languages, so types can have different sizes. Knowing the size of the value at
164+ compile time is important for things like passing it as an argument to a
165+ function, moving it about on the stack and allocating (and deallocating) space
166+ on the heap to store it.
167+
168+ For ` Foo ` , we would need to have a value that could be at least either a
169+ ` String ` (24 bytes) or a ` u8 ` (1 byte), as well as any other type for which
170+ dependent crates may implement ` Foo ` (any number of bytes at all). There's no
171+ way to guarantee that this last point can work if the values are stored without
172+ a pointer, because those other types can be arbitrarily large.
173+
174+ Putting the value behind a pointer means the size of the value is not relevant
175+ when we are tossing a trait object around, only the size of the pointer itself.
145176
146177### Representation
147178
179+ The methods of the trait can be called on a trait object via a special record
180+ of function pointers traditionally called a 'vtable' (created and managed by
181+ the compiler).
182+
183+ Trait objects are both simple and complicated: their core representation and
184+ layout is quite straight-forward, but there are some curly error messages and
185+ surprising behaviors to discover.
186+
148187Let's start simple, with the runtime representation of a trait object. The
149188` std::raw ` module contains structs with layouts that are the same as the
150189complicated built-in types, [ including trait objects] [ stdraw ] :
@@ -265,23 +304,3 @@ let y = TraitObject {
265304If ` b ` or ` y ` were owning trait objects (` Box<Foo> ` ), there would be a
266305` (b.vtable.destructor)(b.data) ` (respectively ` y ` ) call when they went out of
267306scope.
268-
269- ### Why pointers?
270-
271- The use of language like "fat pointer" implies that a trait object is
272- always a pointer of some form, but why?
273-
274- Rust does not put things behind a pointer by default, unlike many managed
275- languages, so types can have different sizes. Knowing the size of the value at
276- compile time is important for things like passing it as an argument to a
277- function, moving it about on the stack and allocating (and deallocating) space
278- on the heap to store it.
279-
280- For ` Foo ` , we would need to have a value that could be at least either a
281- ` String ` (24 bytes) or a ` u8 ` (1 byte), as well as any other type for which
282- dependent crates may implement ` Foo ` (any number of bytes at all). There's no
283- way to guarantee that this last point can work if the values are stored without
284- a pointer, because those other types can be arbitrarily large.
285-
286- Putting the value behind a pointer means the size of the value is not relevant
287- when we are tossing a trait object around, only the size of the pointer itself.
0 commit comments