2015-04-07 21:16:02 -05:00
|
|
|
|
% Trait Objects
|
|
|
|
|
|
|
|
|
|
When code involves polymorphism, there needs to be a mechanism to determine
|
2015-04-21 12:17:43 -05:00
|
|
|
|
which specific version is actually run. This is called ‘dispatch’. There are
|
2015-04-07 21:16:02 -05:00
|
|
|
|
two major forms of dispatch: static dispatch and dynamic dispatch. While Rust
|
|
|
|
|
favors static dispatch, it also supports dynamic dispatch through a mechanism
|
2015-04-21 12:17:43 -05:00
|
|
|
|
called ‘trait objects’.
|
2015-04-07 21:16:02 -05:00
|
|
|
|
|
|
|
|
|
## Background
|
|
|
|
|
|
2015-04-21 12:17:43 -05:00
|
|
|
|
For the rest of this chapter, we’ll need a trait and some implementations.
|
|
|
|
|
Let’s make a simple one, `Foo`. It has one method that is expected to return a
|
2015-04-07 21:16:02 -05:00
|
|
|
|
`String`.
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
trait Foo {
|
|
|
|
|
fn method(&self) -> String;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2015-04-21 12:17:43 -05:00
|
|
|
|
We’ll also implement this trait for `u8` and `String`:
|
2015-04-07 21:16:02 -05:00
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
# trait Foo { fn method(&self) -> String; }
|
|
|
|
|
impl Foo for u8 {
|
|
|
|
|
fn method(&self) -> String { format!("u8: {}", *self) }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Foo for String {
|
|
|
|
|
fn method(&self) -> String { format!("string: {}", *self) }
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Static dispatch
|
|
|
|
|
|
|
|
|
|
We can use this trait to perform static dispatch with trait bounds:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
# trait Foo { fn method(&self) -> String; }
|
|
|
|
|
# impl Foo for u8 { fn method(&self) -> String { format!("u8: {}", *self) } }
|
|
|
|
|
# impl Foo for String { fn method(&self) -> String { format!("string: {}", *self) } }
|
|
|
|
|
fn do_something<T: Foo>(x: T) {
|
|
|
|
|
x.method();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
|
let x = 5u8;
|
|
|
|
|
let y = "Hello".to_string();
|
|
|
|
|
|
|
|
|
|
do_something(x);
|
|
|
|
|
do_something(y);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2015-04-21 12:17:43 -05:00
|
|
|
|
Rust uses ‘monomorphization’ to perform static dispatch here. This means that
|
2015-04-07 21:16:02 -05:00
|
|
|
|
Rust will create a special version of `do_something()` for both `u8` and
|
|
|
|
|
`String`, and then replace the call sites with calls to these specialized
|
|
|
|
|
functions. In other words, Rust generates something like this:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
# trait Foo { fn method(&self) -> String; }
|
|
|
|
|
# impl Foo for u8 { fn method(&self) -> String { format!("u8: {}", *self) } }
|
|
|
|
|
# impl Foo for String { fn method(&self) -> String { format!("string: {}", *self) } }
|
|
|
|
|
fn do_something_u8(x: u8) {
|
|
|
|
|
x.method();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn do_something_string(x: String) {
|
|
|
|
|
x.method();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
|
let x = 5u8;
|
|
|
|
|
let y = "Hello".to_string();
|
|
|
|
|
|
|
|
|
|
do_something_u8(x);
|
|
|
|
|
do_something_string(y);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
This has a great upside: static dispatch allows function calls to be
|
|
|
|
|
inlined because the callee is known at compile time, and inlining is
|
|
|
|
|
the key to good optimization. Static dispatch is fast, but it comes at
|
2015-04-21 12:17:43 -05:00
|
|
|
|
a tradeoff: ‘code bloat’, due to many copies of the same function
|
2015-04-07 21:16:02 -05:00
|
|
|
|
existing in the binary, one for each type.
|
|
|
|
|
|
|
|
|
|
Furthermore, compilers aren’t perfect and may “optimize” code to become slower.
|
|
|
|
|
For example, functions inlined too eagerly will bloat the instruction cache
|
|
|
|
|
(cache rules everything around us). This is part of the reason that `#[inline]`
|
|
|
|
|
and `#[inline(always)]` should be used carefully, and one reason why using a
|
|
|
|
|
dynamic dispatch is sometimes more efficient.
|
|
|
|
|
|
|
|
|
|
However, the common case is that it is more efficient to use static dispatch,
|
|
|
|
|
and one can always have a thin statically-dispatched wrapper function that does
|
|
|
|
|
a dynamic dispatch, but not vice versa, meaning static calls are more flexible.
|
|
|
|
|
The standard library tries to be statically dispatched where possible for this
|
|
|
|
|
reason.
|
|
|
|
|
|
|
|
|
|
## Dynamic dispatch
|
|
|
|
|
|
2015-04-21 12:17:43 -05:00
|
|
|
|
Rust provides dynamic dispatch through a feature called ‘trait objects’. Trait
|
2015-04-07 21:16:02 -05:00
|
|
|
|
objects, like `&Foo` or `Box<Foo>`, are normal values that store a value of
|
|
|
|
|
*any* type that implements the given trait, where the precise type can only be
|
|
|
|
|
known at runtime.
|
|
|
|
|
|
|
|
|
|
A trait object can be obtained from a pointer to a concrete type that
|
|
|
|
|
implements the trait by *casting* it (e.g. `&x as &Foo`) or *coercing* it
|
|
|
|
|
(e.g. using `&x` as an argument to a function that takes `&Foo`).
|
|
|
|
|
|
|
|
|
|
These trait object coercions and casts also work for pointers like `&mut T` to
|
2015-04-21 12:17:43 -05:00
|
|
|
|
`&mut Foo` and `Box<T>` to `Box<Foo>`, but that’s all at the moment. Coercions
|
2015-04-07 21:16:02 -05:00
|
|
|
|
and casts are identical.
|
|
|
|
|
|
2015-04-21 12:17:43 -05:00
|
|
|
|
This operation can be seen as ‘erasing’ the compiler’s knowledge about the
|
2015-04-07 21:16:02 -05:00
|
|
|
|
specific type of the pointer, and hence trait objects are sometimes referred to
|
2015-04-21 12:17:43 -05:00
|
|
|
|
as ‘type erasure’.
|
2015-04-07 21:16:02 -05:00
|
|
|
|
|
|
|
|
|
Coming back to the example above, we can use the same trait to perform dynamic
|
|
|
|
|
dispatch with trait objects by casting:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
# trait Foo { fn method(&self) -> String; }
|
|
|
|
|
# impl Foo for u8 { fn method(&self) -> String { format!("u8: {}", *self) } }
|
|
|
|
|
# impl Foo for String { fn method(&self) -> String { format!("string: {}", *self) } }
|
|
|
|
|
|
|
|
|
|
fn do_something(x: &Foo) {
|
|
|
|
|
x.method();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
|
let x = 5u8;
|
|
|
|
|
do_something(&x as &Foo);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
or by coercing:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
# trait Foo { fn method(&self) -> String; }
|
|
|
|
|
# impl Foo for u8 { fn method(&self) -> String { format!("u8: {}", *self) } }
|
|
|
|
|
# impl Foo for String { fn method(&self) -> String { format!("string: {}", *self) } }
|
|
|
|
|
|
|
|
|
|
fn do_something(x: &Foo) {
|
|
|
|
|
x.method();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
|
let x = "Hello".to_string();
|
|
|
|
|
do_something(&x);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
A function that takes a trait object is not specialized to each of the types
|
|
|
|
|
that implements `Foo`: only one copy is generated, often (but not always)
|
|
|
|
|
resulting in less code bloat. However, this comes at the cost of requiring
|
|
|
|
|
slower virtual function calls, and effectively inhibiting any chance of
|
2015-04-25 09:46:34 -05:00
|
|
|
|
inlining and related optimizations from occurring.
|
2015-04-07 21:16:02 -05:00
|
|
|
|
|
|
|
|
|
### Why pointers?
|
|
|
|
|
|
|
|
|
|
Rust does not put things behind a pointer by default, unlike many managed
|
|
|
|
|
languages, so types can have different sizes. Knowing the size of the value at
|
|
|
|
|
compile time is important for things like passing it as an argument to a
|
|
|
|
|
function, moving it about on the stack and allocating (and deallocating) space
|
|
|
|
|
on the heap to store it.
|
|
|
|
|
|
|
|
|
|
For `Foo`, we would need to have a value that could be at least either a
|
|
|
|
|
`String` (24 bytes) or a `u8` (1 byte), as well as any other type for which
|
2015-04-21 12:17:43 -05:00
|
|
|
|
dependent crates may implement `Foo` (any number of bytes at all). There’s no
|
2015-04-07 21:16:02 -05:00
|
|
|
|
way to guarantee that this last point can work if the values are stored without
|
|
|
|
|
a pointer, because those other types can be arbitrarily large.
|
|
|
|
|
|
|
|
|
|
Putting the value behind a pointer means the size of the value is not relevant
|
|
|
|
|
when we are tossing a trait object around, only the size of the pointer itself.
|
|
|
|
|
|
|
|
|
|
### Representation
|
|
|
|
|
|
|
|
|
|
The methods of the trait can be called on a trait object via a special record
|
2015-04-21 12:17:43 -05:00
|
|
|
|
of function pointers traditionally called a ‘vtable’ (created and managed by
|
2015-04-07 21:16:02 -05:00
|
|
|
|
the compiler).
|
|
|
|
|
|
|
|
|
|
Trait objects are both simple and complicated: their core representation and
|
|
|
|
|
layout is quite straight-forward, but there are some curly error messages and
|
|
|
|
|
surprising behaviors to discover.
|
|
|
|
|
|
2015-04-21 12:17:43 -05:00
|
|
|
|
Let’s start simple, with the runtime representation of a trait object. The
|
2015-04-07 21:16:02 -05:00
|
|
|
|
`std::raw` module contains structs with layouts that are the same as the
|
|
|
|
|
complicated built-in types, [including trait objects][stdraw]:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
# mod foo {
|
|
|
|
|
pub struct TraitObject {
|
|
|
|
|
pub data: *mut (),
|
|
|
|
|
pub vtable: *mut (),
|
|
|
|
|
}
|
|
|
|
|
# }
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
[stdraw]: ../std/raw/struct.TraitObject.html
|
|
|
|
|
|
2015-04-21 12:17:43 -05:00
|
|
|
|
That is, a trait object like `&Foo` consists of a ‘data’ pointer and a ‘vtable’
|
2015-04-07 21:16:02 -05:00
|
|
|
|
pointer.
|
|
|
|
|
|
|
|
|
|
The data pointer addresses the data (of some unknown type `T`) that the trait
|
2015-04-21 12:17:43 -05:00
|
|
|
|
object is storing, and the vtable pointer points to the vtable (‘virtual method
|
|
|
|
|
table’) corresponding to the implementation of `Foo` for `T`.
|
2015-04-07 21:16:02 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
A vtable is essentially a struct of function pointers, pointing to the concrete
|
|
|
|
|
piece of machine code for each method in the implementation. A method call like
|
|
|
|
|
`trait_object.method()` will retrieve the correct pointer out of the vtable and
|
|
|
|
|
then do a dynamic call of it. For example:
|
|
|
|
|
|
2015-04-21 12:17:43 -05:00
|
|
|
|
```rust,ignore
|
2015-04-07 21:16:02 -05:00
|
|
|
|
struct FooVtable {
|
|
|
|
|
destructor: fn(*mut ()),
|
|
|
|
|
size: usize,
|
|
|
|
|
align: usize,
|
|
|
|
|
method: fn(*const ()) -> String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// u8:
|
|
|
|
|
|
|
|
|
|
fn call_method_on_u8(x: *const ()) -> String {
|
|
|
|
|
// the compiler guarantees that this function is only called
|
|
|
|
|
// with `x` pointing to a u8
|
|
|
|
|
let byte: &u8 = unsafe { &*(x as *const u8) };
|
|
|
|
|
|
|
|
|
|
byte.method()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Foo_for_u8_vtable: FooVtable = FooVtable {
|
|
|
|
|
destructor: /* compiler magic */,
|
|
|
|
|
size: 1,
|
|
|
|
|
align: 1,
|
|
|
|
|
|
|
|
|
|
// cast to a function pointer
|
|
|
|
|
method: call_method_on_u8 as fn(*const ()) -> String,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// String:
|
|
|
|
|
|
|
|
|
|
fn call_method_on_String(x: *const ()) -> String {
|
|
|
|
|
// the compiler guarantees that this function is only called
|
|
|
|
|
// with `x` pointing to a String
|
|
|
|
|
let string: &String = unsafe { &*(x as *const String) };
|
|
|
|
|
|
|
|
|
|
string.method()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Foo_for_String_vtable: FooVtable = FooVtable {
|
|
|
|
|
destructor: /* compiler magic */,
|
|
|
|
|
// values for a 64-bit computer, halve them for 32-bit ones
|
|
|
|
|
size: 24,
|
|
|
|
|
align: 8,
|
|
|
|
|
|
|
|
|
|
method: call_method_on_String as fn(*const ()) -> String,
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The `destructor` field in each vtable points to a function that will clean up
|
2015-06-01 16:06:23 -05:00
|
|
|
|
any resources of the vtable’s type: for `u8` it is trivial, but for `String` it
|
2015-04-07 21:16:02 -05:00
|
|
|
|
will free the memory. This is necessary for owning trait objects like
|
|
|
|
|
`Box<Foo>`, which need to clean-up both the `Box` allocation as well as the
|
|
|
|
|
internal type when they go out of scope. The `size` and `align` fields store
|
|
|
|
|
the size of the erased type, and its alignment requirements; these are
|
|
|
|
|
essentially unused at the moment since the information is embedded in the
|
|
|
|
|
destructor, but will be used in the future, as trait objects are progressively
|
|
|
|
|
made more flexible.
|
|
|
|
|
|
2015-06-01 16:06:23 -05:00
|
|
|
|
Suppose we’ve got some values that implement `Foo`. The explicit form of
|
2015-04-07 21:16:02 -05:00
|
|
|
|
construction and use of `Foo` trait objects might look a bit like (ignoring the
|
2015-04-21 12:17:43 -05:00
|
|
|
|
type mismatches: they’re all just pointers anyway):
|
2015-04-07 21:16:02 -05:00
|
|
|
|
|
2015-04-21 12:17:43 -05:00
|
|
|
|
```rust,ignore
|
2015-04-07 21:16:02 -05:00
|
|
|
|
let a: String = "foo".to_string();
|
|
|
|
|
let x: u8 = 1;
|
|
|
|
|
|
|
|
|
|
// let b: &Foo = &a;
|
|
|
|
|
let b = TraitObject {
|
|
|
|
|
// store the data
|
|
|
|
|
data: &a,
|
|
|
|
|
// store the methods
|
|
|
|
|
vtable: &Foo_for_String_vtable
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// let y: &Foo = x;
|
|
|
|
|
let y = TraitObject {
|
|
|
|
|
// store the data
|
|
|
|
|
data: &x,
|
|
|
|
|
// store the methods
|
|
|
|
|
vtable: &Foo_for_u8_vtable
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// b.method();
|
|
|
|
|
(b.vtable.method)(b.data);
|
|
|
|
|
|
|
|
|
|
// y.method();
|
|
|
|
|
(y.vtable.method)(y.data);
|
|
|
|
|
```
|
2015-08-05 11:58:49 -05:00
|
|
|
|
|
|
|
|
|
## Object Safety
|
|
|
|
|
|
|
|
|
|
Not every trait can be used to make a trait object. For example, vectors implement
|
|
|
|
|
`Clone`, but if we try to make a trait object:
|
|
|
|
|
|
|
|
|
|
```ignore
|
|
|
|
|
let v = vec![1, 2, 3];
|
|
|
|
|
let o = &v as &Clone;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
We get an error:
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
error: cannot convert to a trait object because trait `core::clone::Clone` is not object-safe [E0038]
|
|
|
|
|
let o = &v as &Clone;
|
|
|
|
|
^~
|
|
|
|
|
note: the trait cannot require that `Self : Sized`
|
|
|
|
|
let o = &v as &Clone;
|
|
|
|
|
^~
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The error says that `Clone` is not ‘object-safe’. 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`
|
|
|
|
|
|
|
|
|
|
Whew! As we can see, almost all of these rules talk about `Self`. A good intuition
|
|
|
|
|
is “except in special circumstances, if your trait’s method uses `Self`, it is not
|
|
|
|
|
object-safe.”
|