Traits

Generics allow a program to reuse the same algorithm on different data types. However, sometimes an algorithm applies to only certain types.


# #![allow(unused_variables)]
#fn main() {
struct Rectangle {
    x: i32,
    y: i32,
    w: i32,
    h: i32,
}

impl Rectangle {
    fn area(&self) -> i32 {
        self.w * self.h
    }
}

struct Square {
    x: i32,
    y: i32,
    w: i32,
}

impl Square {
    fn area(&self) -> i32 {
        self.w * self.w
    }
}

// Error: `T` is not guaranteed to have the `area()` method.
fn print_area<T>(x: T) {
    println!("{}", x.area());
}
#}

In the above program, the function print_area() declares no restriction on the type parameter T, but not all types have the method area(). For example, it makes no sense for the program to call print_area(1), because i32 has no method area(). Rust provides traits to restrict what concrete types may instantiate a type parameter.

Trait and trait bound

A trait U declares a set of methods that a type must implement. When a type V implements U, it must implement all of U's methods in an implementation block. Similar to the implementation block of a struct, in the implementation block of a trait, the program may use the keyword Self to refer to the type V, and a method f may use the keyword self to refer to the value on which f is called.

When the program declares a type parameter T, it can specify that only types that implement a trait U may instantiate T. In other words, the program places a trait bound U on T.


# #![allow(unused_variables)]
#fn main() {
trait Area {
    fn area(&self) -> i32;
    fn new() -> Self;
}

struct Rectangle {
    x: i32,
    y: i32,
    w: i32,
    h: i32,
}

impl Area for Rectangle {
    fn area(&self) -> i32 {
        self.w * self.h
    }

    fn new() -> Self {
        Rectangle{ x: 0, y: 0, w: 1, h: 1 }
    }
}

struct Square {
    x: i32,
    y: i32,
    w: i32,
}

impl Area for Square {
    fn area(&self) -> i32 {
        self.w * self.w
    }

    fn new() -> Self {
        Square{ x: 0, y: 0, w: 1 }
    }
}

// OK: `T` is bounded by `Area`.
fn print_area<T: Area>(x: T) {
    println!("{}", x.area());
}

// OK: `T` is bounded by `Area`.
fn new_area<T: Area>() -> T {
    // Since `new()` doesn't take any value, we must call `new()` directly.
    T::new()
}

// Need either type declaration or type hint for x
let x: Rectangle = new_area();
let y = new_area::<Square>();
// Ok
print_area(x);
print_area(y);
// Error: `i32` doesn't implement `Area`.
// print_area(1);
// Error: cannot infer the concrete type for the type parameter
// let x = new_area();
#}

Multiple trait bounds

A type may implement multiple traits, and a type parameter may require multiple trait bounds.


# #![allow(unused_variables)]
#fn main() {
trait Area {
    fn area(&self) -> i32;
    fn new() -> Self;
}

trait Transform {
    fn scale(&mut self, s: i32);
}

struct Square {
    x: i32,
    y: i32,
    w: i32,
}

impl Area for Square {
    fn area(&self) -> i32 {
        self.w * self.w
    }

    fn new() -> Self {
        Square{ x: 0, y: 0, w: 1 }
    }
}

impl Transform for Square {
    fn scale(&mut self, s: i32) {
        self.w *= s;
    }
}

// A concrete type for `T` must implement both `Area` and `Transform`
fn scale<T: Area + Transform>(mut x: T, s: i32) {
    println!("Before scale: {}", x.area());
    x.scale(s);
    println!("After scale: {}", x.area());
}

fn new_area<T: Area>() -> T {
    // Since `new()` doesn't take any value, we must call `new()` directly.
    T::new()
}

let mut x: Square = new_area();
scale(x, 2);
#}

where clause

Instead of specifying the trait bounds when declaring a type parameter, a method may specify the trait bounds after declaring all the parameters and return type, in a where clause.


# #![allow(unused_variables)]
#fn main() {
// Equivalent to `fn scale<T: Area + Transform>(mut x: T, s: i32)`
fn scale<T>(mut x: T, s: i32) where T: Area + Transform {
    println!("Before scale: {}", x.area());
    x.scale(s);
    println!("After scale: {}", x.area());
}
#}

The where clause is more expressive because more than just the type parameter may appear at the left of the colon.

trait MyInto<T> {
    fn my_into(self) -> T;
}

impl MyInto<String> for i32 {
    fn my_into(self) -> String {
        format!("{}", self)
    }
}

impl MyInto<bool> for i32 {
    fn my_into(self) -> bool {
        self != 0
    }
}

fn inverse_convert<T>(x: i32) -> T
    where i32: MyInto<T>
{
    (-x).my_into()
}

fn main() {
    let x: bool = inverse_convert(1);
    assert_eq!(x, true);
    let x: String = inverse_convert(1);
    assert_eq!(x, "-1");
}

Default methods

A trait declares a set of methods, and a type implementing the trait must implement all the methods. Sometimes, a trait may provide default implementation of some methods. If a type implementing the trait does not accept the default implementation, it may reimplement these methods.


# #![allow(unused_variables)]
#fn main() {
trait PartialEq {
    fn eq(&self, other: &Self) -> bool;
    
    // Default method
    fn ne(&self, other: &Self) -> bool {
        !self.eq(other)
    }
}

struct Square {
    x: i32,
    y: i32,
    w: i32,
}

impl PartialEq for Square {
    fn eq(&self, other: &Self) -> bool {
        self.x == other.x && self.y == other.y && self.w == other.w
    }
    
    // Accepts the default method `PartialEq::ne()`, so does not reimplement it.
}

struct Rectangle {
    x: i32,
    y: i32,
    w: i32,
    h: i32,
}

impl PartialEq for Rectangle {
    fn eq(&self, other: &Self) -> bool {
        self.x == other.x && self.y == other.y && self.w == other.w && self.h == other.h
    }
    
    // Reimplements the default method `ne()`.
    fn ne(&self, other: &Self) -> bool {
        self.x != other.x || self.y != other.y || self.w != other.w || self.h != other.h
    }
}
#}

Inheritance

A trait A may inherit another trait B, meaning that any type implementing A must implement all the methods in both A and B.


# #![allow(unused_variables)]
#fn main() {
trait Area {
    fn new() -> Self;
    fn area(&self) -> i32;
}

// Any type implementing `Equal` must also implement `Area`
trait Equal: Area {
    fn eq(&self, other: &Self) -> bool;
    fn ne(&self, other: &Self) -> bool {
        !self.eq(other)
    }
}

struct Square {
    x: i32,
    y: i32,
    w: i32,
}

impl Area for Square {
    fn area(&self) -> i32 {
        self.w * self.w
    }

    fn new() -> Self {
        Square{ x: 0, y: 0, w: 1 }
    }
}

impl Equal for Square {
    fn eq(&self, other: &Self) -> bool {
        self.area() == other.area()
    }
}
#}

Special traits

Deref


# #![allow(unused_variables)]
#fn main() {
trait Deref {
    type Target: ?Sized;
    fn deref(&self) -> &Self::Target;
}
#}

This trait enables deref coercion: If impl Deref<Target=U> for T, then the compiler automatically coerces &T to &U by calling Deref::deref(). For example, if t: T, then the compiler coerces &t to t.deref().

If a variable t is not a reference, the compiler automatically takes a reference of t in the following context:

  • *t: the compiler changes this expression to *((&t).deref()).
  • t.f(): the compiler changes this expression to (&t).deref().f().

# #![allow(unused_variables)]
#fn main() {
use std::ops::Deref;

struct Foo<'a>(&'a str);

impl<'a> Deref for Foo<'a> {
    type Target = str;
    fn deref<'b>(&'b self) -> &'b Self::Target {
        self.0
    }
}

let s = "Rust";
let foo = Foo(s);
assert_eq!(s.len(), foo.len());
#}

A similar trait is DerefMut, which also enables deref coercion.

trait DerefMut: Deref {
    fn deref_mut(&mut self) -> &mut Self::Target;
}

Deref coercion is commonly used to abstract the distinction between borrowed values and owned values when the distinction is inconsequential to the code in question. Rust provides three types of pointers:

These pointers differ in how the program manages the memory of their values. But sometimes a function only accesses the values without caring about their memory management. In this case, it would be laborious to write two versions of the same function where the only difference is the type of pointers that the function takes.


# #![allow(unused_variables)]
#fn main() {
fn f(x: &i32) {
    println!("{}", x);
}

fn g(x: &Box<i32>) {
    println!("{}", x);
}

fn h() {
    f(&1);
    g(&Box::new(2));
}
#}

It would be nice to write only one function that can handle both borrowed and owned pointers. To allow this, Rust's standard library implements the Deref trait for Box:


# #![allow(unused_variables)]
#fn main() {
impl<T> Deref for Box<T> where T: ?Sized {
    type Target = T;
}
#}

This instructs the compiler to coerce &Box<T> to &T. Therefore, we can remove the function g() and the above program still compiles.


# #![allow(unused_variables)]
#fn main() {
fn f(x: &i32) {
    println!("{}", x);
}

fn h() {
    f(&1);
    f(&Box::new(2));
}
#}

Another example of borrowed vs. owned pointers is Rust's strings. Rust provides two types of strings: &str points to a borrowed static string, and String points to an owned string on the heap. String implements the Deref trait to allow deref coercion to coerce &String to &str.


# #![allow(unused_variables)]
#fn main() {
impl Deref for String {
    type Target = str;
}
#}

# #![allow(unused_variables)]
#fn main() {
fn f(x: &str) {
    println!("{}", x);
}

fn g() {
    // The type of `x` is `&str`.
    let x = "abc";
    f(x);
    // The type of `y` is `String`.
    let y = "def".to_owned();
    f(&y);
}
#}

Deref coercion happens only when type mismatch would happen without the coercion.


# #![allow(unused_variables)]
#fn main() {
fn f(x: &str) {
    println!("{}", x);
}

fn g(x: &String) {
    println!("{}", x);
}

fn h() {
    // The type of `x` is `String`.
    let x = "abc".to_owned();
    // Deref coercion happens.
    f(&x);
    // Deref coercion does not happen.
    g(&x);
}
#}

Other compiler-aware traits

Besides Deref and DerefMut, a few other traits also change the behavior of the compiler.

  • Copy

By default, Rust moves values during assignment. This is called move semantics. However, if a type implements the Copy trait, Rust copies its values during assignment instead.

  • Drop

The Drop trait provides a destructor. If a type implements the Drop trait, Rust invokes the drop function when its value disappears, either because it goes out of scope or because it is overwritten.


# #![allow(unused_variables)]
#fn main() {
trait Drop {
    fn drop(&mut self);
}
#}

For example,


# #![allow(unused_variables)]
#fn main() {
struct Foo(i32);

impl Drop for Foo {
    fn drop(&mut self) {
        println!("{} dropped.", self.0);
    }
}

{
    let foo = Foo(1);
    // Prints `1 dropped`
}

#}
  • Sized

The sizes of most types are known at compile time. However, the sizes of some types, such as [T] and str, aren't. They are called unsized or dynamically sized types. There are restrictions on these types.

  • The program may manipulate the value of an unsized type only via a pointer.
  • Variables and parameters may not be unsized.
  • Only the last field in a struct may be unsized.

The Sized trait indicates that a type is sized. Since most types are sized, all type parameters have the implicit Sized bound (i.e., T: Sized is implicit for all type parameters T). For example, the following code doesn't compile:


# #![allow(unused_variables)]
#fn main() {
fn f<T>(t: &T) {
}

let a = [1, 2, 3];
// `a[..]` is of type `[i32]` and is therefore unsized.
// So this does not compile.
f(&a[..]);
#}

The ?Sized trait overrides the implicit Sized bound. It allows the concrete type to be unsized.


# #![allow(unused_variables)]
#fn main() {
fn f<T: ?Sized>(t: &T) {
}

let a = [1, 2, 3];
// `a[..]` is of type `[i32]` and is therefore unsized.
// Compiles because `T: ?Sized`
f(&a[..]);
#}

Derivable traits

A program can request the compiler to automatically implement certain traits for a type by applying the derive attribute to the type declaration and specifying the traits in the attribute.


# #![allow(unused_variables)]
#fn main() {
// Must derive both `Copy` and `Clone` because `Copy` inherits `Clone`.
#[derive(Copy, Clone)]
struct Foo(i32);

let x = Foo(1);
// `x` is copied rather than moved.
let y = x;
assert_eq!(x.0, y.0);
#}

The following traits are derivable:

  • Clone: for manually copying values.
  • Copy: for selecting copy (instead of move) semantics in assignment.
  • Debug: for println!("{:?}", ...)
  • Default: for creating default values for the type.
  • For comparisons:
    • PartialEq
    • Eq
    • PartialOrd
    • Ord
  • Hash: for HashMap and HashSet.

Associated types

Just like structs, traits may be generic. The following program may instantiate the type parameters T with many concrete types to create different concrete traits.


# #![allow(unused_variables)]
#fn main() {
trait MyInto<T> {
    fn my_into(self) -> T;
}

impl MyInto<String> for i32 {
    fn my_into(self) -> String {
        format!("{}", self)
    }
}

impl MyInto<bool> for i32 {
    fn my_into(self) -> bool {
        self != 0
    }
}

let x = 1;
let y: String = x.my_into();
assert_eq!(y, "1");
let y: bool = x.my_into();
assert_eq!(y, true);
#}

However, sometimes a program may wish to include type parameters in a trait, but for each type implementing the trait, it must instantiate the type parameter in only one way. For example, the trait Deref provides deref coercion, but for each type T implementing Deref, it must specify a single concrete type U where &T automatically coerces to &U.

Consider the following incorrect declaration of Deref:

// Incorrect
trait Deref<T> {
    fn deref(&self) -> &T;
}

It fails to satisfy the above requirement, because it would allow many different Us where &T coerces to &U as in the following:

// Incorrect
impl Deref<str> for String {
    fn deref(&self) -> &str { ... }
}

impl Deref<i32> for String {
    fn deref(&self) -> &i32 { ... }
}

Looking from another perspective, in MyInto<T>, the type parameter T is an input type parameter to the trait MyInto, so T may be instantiated into many different concrete types. By contrast, in Deref, the type parameter T is an output type parameter, in the sense that the concrete type implementing Deref determines a unique concrete type for T. Rust provides associated types to denote output type parameters:


# #![allow(unused_variables)]
#fn main() {
trait Deref {
    type Target;
    fn deref(&self) -> &Self::Target;
}
#}

A program declares an associated type using the keyword type in a trait declaration. When a type implements the trait, it must instantiate the associated type:

impl Deref for String {
    type Target = str;
    fn deref(&self) -> &Self::Target {
        ....
    }
}

Autoref, autoderef, and deref coercion

Rust prefers explicitness. Therefore, it does implicit conversion only in a few cases. Notably, in certain cases Rust automatically takes references (autoref), dereferences (autoderef), and converts between different types of references (deref coercion). These conversions happen only when type mismatch would occur without them.

Autoref

  • If f(&self, ...) is a method implemented for the type T, and t is a variable of T, then the compiler converts t.f() to (&t).f(), or equivalently T::f(&t, ...).

  • If t is a non-reference variable of type T and impl Deref for T, then the compiler converts *t to *(&t) and then applies deref coercion on &t.

Autoderef

  • If f(self, ...) or f(&self, ...) is a method implemented for the type T, and t is a reference to T in one or more levels (e.g., &T, &&T, etc.), then the compiler dereferences t as many times as necessary to make the resulting type match that of the first parameter of f. E.g., if t: &&&T and f(&self, ...), then the compiler converts t.f() to (**t).f(), or equivalently T::f(**t).

Deref coercion

If impl Deref<Target=U> for T and t is a variable of the type T, then the compiler coerces &t, either written explicitly in the program or resulting from autoref, to (&t).deref(), which is of type &U.