]> git.lizzy.rs Git - rust.git/commitdiff
Add documentation on trait objects.
authorSteve Klabnik <steve@steveklabnik.com>
Mon, 9 Feb 2015 01:15:16 +0000 (20:15 -0500)
committerSteve Klabnik <steve@steveklabnik.com>
Tue, 10 Feb 2015 02:38:49 +0000 (21:38 -0500)
Largely taken from @huonw's
http://huonw.github.io/blog/2015/01/peeking-inside-trait-objects/

Fixes #21707

src/doc/trpl/SUMMARY.md
src/doc/trpl/static-and-dynamic-dispatch.md [new file with mode: 0644]
src/doc/trpl/traits.md

index 9d65f30e7237993e2d2793dc410644e664019ed3..bfc1247dc3bc074002fd1de4721729cf0e9c25ed 100644 (file)
@@ -27,6 +27,7 @@
     * [Iterators](iterators.md)
     * [Generics](generics.md)
     * [Traits](traits.md)
+    * [Static and Dynamic Dispatch](static-and-dynamic-dispatch.md)
     * [Concurrency](concurrency.md)
     * [Error Handling](error-handling.md)
     * [Documentation](documentation.md)
diff --git a/src/doc/trpl/static-and-dynamic-dispatch.md b/src/doc/trpl/static-and-dynamic-dispatch.md
new file mode 100644 (file)
index 0000000..9421dac
--- /dev/null
@@ -0,0 +1,286 @@
+% Static and Dynamic Dispatch
+
+When code involves polymorphism, there needs to be a mechanism to determine
+which specific version is actually run. This is called 'dispatch.' There are
+two major forms of dispatch: static dispatch and dynamic dispatch. While Rust
+favors static dispatch, it also supports dynamic dispatch through a mechanism
+called 'trait objects.'
+
+## Background
+
+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
+`String`.
+
+```rust
+trait Foo {
+    fn method(&self) -> String;
+}
+```
+
+We'll also implement this trait for `u8` and `String`:
+
+```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);
+}
+```
+
+Rust uses 'monomorphization' to perform static dispatch here. This means that
+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 some upsides: static dispatching of any method calls, allowing for
+inlining and hence usually higher performance. It also has some downsides:
+causing code bloat due to many copies of the same function existing in the
+binary, one for each type.
+
+Furthermore, compilers aren’t perfect and may “optimise” 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, 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
+
+Rust provides dynamic dispatch through a feature called 'trait objects.' Trait
+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. The methods of the trait can be called on a trait object via
+a special record of function pointers (created and managed by the compiler).
+
+A function that takes a trait object is not specialised 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
+inlining and related optimisations from occurring.
+
+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 behaviours to discover.
+
+### Obtaining a trait object
+
+There's two similar ways to get a trait object value: casts and coercions. If
+`T` is a type that implements a trait `Foo` (e.g. `u8` for the `Foo` above),
+then the two ways to get a `Foo` trait object out of a pointer to `T` look
+like:
+
+```{rust,ignore}
+let ref_to_t: &T = ...;
+
+// `as` keyword for casting
+let cast = ref_to_t as &Foo;
+
+// using a `&T` in a place that has a known type of `&Foo` will implicitly coerce:
+let coerce: &Foo = ref_to_t;
+
+fn also_coerce(_unused: &Foo) {}
+also_coerce(ref_to_t);
+```
+
+These trait object coercions and casts also work for pointers like `&mut T` to
+`&mut Foo` and `Box<T>` to `Box<Foo>`, but that's all at the moment. Coercions
+and casts are identical.
+
+This operation can be seen as "erasing" the compiler's knowledge about the
+specific type of the pointer, and hence trait objects are sometimes referred to
+"type erasure".
+
+### Representation
+
+Let's start simple, with the runtime representation of a trait object. The
+`std::raw` module contains structs with layouts that are the same as the
+complicated build-in types, [including trait objects][stdraw]:
+
+```rust
+# mod foo {
+pub struct TraitObject {
+    pub data: *mut (),
+    pub vtable: *mut (),
+}
+# }
+```
+
+[stdraw]: ../std/raw/struct.TraitObject.html
+
+That is, a trait object like `&Foo` consists of a "data" pointer and a "vtable"
+pointer.
+
+The data pointer addresses the data (of some unknown type `T`) that the trait
+object is storing, and the vtable pointer points to the vtable ("virtual method
+table") corresponding to the implementation of `Foo` for `T`.
+
+
+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:
+
+```{rust,ignore}
+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
+any resources of the vtable's type, for `u8` it is trivial, but for `String` it
+will free the memory. This is necessary for owning trait objects like
+`Box<Foo>`, which need to clean-up both the `Box` allocation and 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 future, as trait objects are progressively made
+more flexible.
+
+Suppose we've got some values that implement `Foo`, the explicit form of
+construction and use of `Foo` trait objects might look a bit like (ignoring the
+type mismatches: they're all just pointers anyway):
+
+```{rust,ignore}
+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);
+```
+
+If `b` or `y` were owning trait objects (`Box<Foo>`), there would be a
+`(b.vtable.destructor)(b.data)` (respectively `y`) call when they went out of
+scope.
+
+### Why pointers?
+
+The use of language like "fat pointer" implies that a trait object is
+always a pointer of some form, but why?
+
+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
+dependent crates may implement `Foo` (any number of bytes at all). There's no
+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.
index d12480d7dd9fa9339c6912a6e02abab81531db2d..e091878cf86ec200df19b5b1878adebf7969ef64 100644 (file)
@@ -270,51 +270,8 @@ not, because both the trait and the type aren't in our crate.
 
 One last thing about traits: generic functions with a trait bound use
 *monomorphization* (*mono*: one, *morph*: form), so they are statically
-dispatched. What's that mean? Well, let's take a look at `print_area` again:
-
-```{rust,ignore}
-fn print_area<T: HasArea>(shape: T) {
-    println!("This shape has an area of {}", shape.area());
-}
-
-fn main() {
-    let c = Circle { ... };
-
-    let s = Square { ... };
-
-    print_area(c);
-    print_area(s);
-}
-```
-
-When we use this trait with `Circle` and `Square`, Rust ends up generating
-two different functions with the concrete type, and replacing the call sites with
-calls to the concrete implementations. In other words, you get something like
-this:
-
-```{rust,ignore}
-fn __print_area_circle(shape: Circle) {
-    println!("This shape has an area of {}", shape.area());
-}
-
-fn __print_area_square(shape: Square) {
-    println!("This shape has an area of {}", shape.area());
-}
-
-fn main() {
-    let c = Circle { ... };
-
-    let s = Square { ... };
-
-    __print_area_circle(c);
-    __print_area_square(s);
-}
-```
-
-The names don't actually change to this, it's just for illustration. But
-as you can see, there's no overhead of deciding which version to call here,
-hence *statically dispatched*. The downside is that we have two copies of
-the same function, so our binary is a little bit larger.
+dispatched. What's that mean? Check out the chapter on [static and dynamic
+dispatch](static-and-dynamic-dispatch.html) for more.
 
 ## Our `inverse` Example