Rust, typed: traits and trait objects

What is a trait?

In Rust, a trait is a group of associated types, functions, and methods that a concrete type may implement. It defines a sort-of abstract interface that can be used to indirectly refer to some behaviour of the full type. After definition and implementation, a trait may be used as a bound on a generic type or as an entirely stand-alone object called a trait object.

What is a trait bound?

When using a generic type, it may be necessary to require it to implement the interface of a given type. These requirements are called trait bounds. Only by setting a trait bound on a generic type will it then be safe to use the interface of a trait through the implementing type. When using generic types with trait bounds, the implementing type will still be known directly to the generic code. This erasure of traits into types is called monomorphization.

trait MyTrait {
   fn do_something(&self);
}
struct MyType;
impl MyTrait for MyType {
   fn do_something(&self) {
      println!("do");
   }
}
fn f<T: MyTrait>(t: T) {
   t.do_something();
}

fn main() {
   f(MyType{});
}

What is a trait object?

When you want to avoid monomorphization of code, or when it is impossible to access the implementing type, you may want to use a feature called trait objects. A trait object is a dynamic interface to a type's implemented traits that does not require knowledge of the underlying type. This is often referred to simply as a dynamic type.

trait MyTrait {
   fn do_something(&self);
}
struct MyType;
impl MyTrait for MyType {
   fn do_something(&self) {
      println!("do");
   }
}
fn f(t: Box<dyn MyTrait>) {
   t.do_something();
}

fn main() {
   f(Box::new(MyType{}));
}

These two patterns account for most of the type complexity of Rust. If you understand both, then you should be well on your way to understanding most code that is written in Rust.