@@ -102,49 +102,88 @@ reason.
102
102
Rust provides dynamic dispatch through a feature called 'trait objects.' Trait
103
103
objects, like ` &Foo ` or ` Box<Foo> ` , are normal values that store a value of
104
104
* 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.
107
106
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 ` ).
113
110
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 .
117
114
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".
119
118
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:
124
121
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 ) } }
127
126
128
- // `as` keyword for casting
129
- let cast = ref_to_t as &Foo;
127
+ fn do_something (x : & Foo ) {
128
+ x . method ();
129
+ }
130
130
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
+ ```
133
136
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
+ }
136
152
```
137
153
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.
141
159
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.
145
176
146
177
### Representation
147
178
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
+
148
187
Let's start simple, with the runtime representation of a trait object. The
149
188
` std::raw ` module contains structs with layouts that are the same as the
150
189
complicated built-in types, [ including trait objects] [ stdraw ] :
@@ -265,23 +304,3 @@ let y = TraitObject {
265
304
If ` b ` or ` y ` were owning trait objects (` Box<Foo> ` ), there would be a
266
305
` (b.vtable.destructor)(b.data) ` (respectively ` y ` ) call when they went out of
267
306
scope.
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