Utility Traits

Drop

You can customize hwo Rust drops values of your type by implementing the std::ops::Drop trait:


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

Implicit Behavior: The drop method of the Drop trait is called implicity by Rust, if you try to call it yourself, it'll be flagged as an error.

You'll never need to implement Drop unless you're defining a type that owns resources Rust doesn't already know about.

Warning: If a type implements Drop, it cannot implement Copy.

Sized

Term: A type whose values all have the same size in memory is called a sized type. In Rust, almost all types are sized types.

All sized types implement the std::marker::Sized trait, which has no methods nor associated types. Rust implements it automatically for all types to which it applies; you can't implement it yourself.

Use Case: The only use for the Sized trait is as a bound for type variables: a bound like T: Sized requires T to be a type whose size is known at compile time.

Term: A trait that can only be used as a type parameter bound, and cannot be explicitly implemented (like Sized), is called a marker trait.

Implicit Behavior: Since unsized types are so limited, Rust implicitly assumes that generic type parameters have a Sized bound. This mean that when you write struct S<T>, Rust assumes you mean struct S<T: Sized>.

Syntax: Since Rust assumes all type parameters have a Sized bound, you have to explicitly opt-out of it using the ?Sized syntax: struct S<T: ?Sized>.

Clone

The std::clone::Clone trait is for types that can make copies of themselves. It's a subtrait of Sized and is defined like this:


#![allow(unused)]
fn main() {
trait Clone: Sized {
    fn clone(&self) -> Self;
    fn clone_from(&mut self, source: &Self) {
        *self = source.clone()
    }
}
}

Warning: Cloning values can be computationally expensive!

The clone_from method modifies self into a copy of source.

Convention: In generic code, you should use clone_from whenever possible.

Tip: If your Clone implementation simply applies clone to each field of your type, then Rust can implement it for you by adding #[derive(Clone)] above your type definition.

Warning: The clone method of types that implement Clone must be infallible!

Copy

A type is Copy if it implements the std::marker::Copy marker trait, a subtrait of Clone and defined as:


#![allow(unused)]
fn main() {
trait Copy: Clone {}
}

Tip: Like Clone, Copy can be automatically implemented using #[derive(Copy)].

Deref and DerefMut

You can specify how dereferencing operators like * and . behave on your types by implementing the std::ops::Deref and std::ops::DerefMut traits:


#![allow(unused)]
fn main() {
trait Deref {
    type Target: ?Sized;
    fn deref(&self) -> &Self::Target;
}
// DerefMut is a subtrait of Deref
trait DerefMut: Deref {
    fn deref_mut(&mut self) -> &mut Self::Target;
}
}

Term: If inserting a deref call prevents a type mismatch, Rust insterts one for you. These are called deref coercions: one type is "coerced" into behaving as another.

Add notes about the implications of deref coercions.

Rust will apply several deref coercions in succession if necessary.

Use Case: The Deref and DerefMut traits are designed for implementing smart pointer types like Box, Rc, and Arc, and types that serve as owning versions of something you would frequently use by reference, the way Vec<T> and String serve as owning versions of [T] and [str].

Anti-Pattern: Do not implement Deref and DerefMut for a type just to make the Target type's methods appear on it automatically, the way a C++ base class's methods are visible on a subclass.

Warning: Rust applies deref coercions to resolve type conflicts, but it does not apply them to satisfy bounds on type variables.

Example

Say we have a struct called Selector<T> that has a field elements: Vec<T> and a field named current: usize that behaves like a pointer to the current element.

Given a value s of type Selector<T>, we want to be able to do these things:

  1. Use the expression *s to get the value of the current element
  2. Apply methods implemented by the type of the currently pointed to element
  3. Change the value of the currently pointed to element with *s = '?'

#![allow(unused)]
fn main() {
use std::ops::{Deref, DerefMut};

struct Selector<T> {
    elements: Vec<T>,
    current: usize,
}

// Implementing Deref allows us to use *s to get the current element
impl<T> Deref for Selector<T> {
    type Target = T;
    fn deref(&self) -> &T {
        &self.elements[self.current]
    }
}

// Implementing DerefMut allows us to set the value of the current element
impl<T> DerefMut for Selector<T> {
    fn deref_mut(&mut self) -> &mut T {
        &mut self.elements[self.current]
    }
}

let mut s = Selector { elements: vec!['x', 'y', 'z'], current: 2 };
println!("current element {}", *s);
println!("is alphabetic? {}", s.is_alphabetic());
*s = 'w';
println!("current element {}", *s);
}

Default

Types with a reasonably obvious default value can implement the std::default::Default trait:


#![allow(unused)]
fn main() {
trait Default {
    fn default() -> Self;
}
}

All of Rust's collection types (like Vec, HashMap, BinaryHeap, etc) implement Default, with default methods that return an empty collection.

Use Case: Default is commonly used to produce default values for structs that represent a large collection of parameters, most of which you won't usually want to change. (think options objects in JS)

A perfect example of making good use of Default is when using the OpenGL crate called glium. Drawing with OpenGL requires a ton of parameters, most of which you don't care about. So, we can use Default to provide those parameters for us:


#![allow(unused)]
fn main() {
let params = glium::DrawParameters {
    line_width: Some(0.02),
    point_size: Some(0.02),
    .. Default::default(),
}

target.draw(..., &params).unwrap();
}

Tip: Rust does not implicitly implement Default for struct types, but if all of a struct's fields implement Default, you can implement Default for the struct automatically using #[derive(Default)].

AsRef and AsMut

When a type implements AsRef<T>, that means you can borrow a &T from it efficiently; AsMut is the analogue for mutable references:


#![allow(unused)]
fn main() {
trait AsRef<T: ?Sized> {
    fn as_ref(&self) -> &T;
}
trait AsMut<T: ?Sized> {
    fn as_ref(&mut self) -> &mut T;
}
}

Use Case: AsRef is typically used to make functions more flexible in the argument types they accept. For instance, std::fs::File::open is declared like this:


#![allow(unused)]
fn main() {
fn open<P: AsRef<Path>>(path: P) -> Result<File>;
}

Concept: You can think of using AsRef as a type bound as a bit like function overloading.

In the above use case, what open really wants is a &Path, the type representing a filesystem path. But with the above declaration, open accepts anything it can borrow a &Path from--that is, anything that implements AsRef<Path>.

Use Case: It only makes sense for a type to implement AsMut<T> if modifying the given T cannot violate the type's invariants.

Anti-Pattern: You should avoid defining your own AsFoo traits when you could just implement AsRef<Foo> instead.

Borrow and BorrowMut

The std::borrow::Borrow trait is similar to AsRef: if a type implements Borrow<T>, then its borrow method efficiently borrows a &T from it. But Borrow imposes more restrictions.

Use Case: A type should implement Borrow<T> ony when a &T hashes and compares the same way as the value it's borrowed from. Borrow is valuable in dealing with keys in hash tables and trees, or when dealing with values that will be hashed or compared for some other reason.

Come back to the example implementation related to   HashMap.

From and Into

The std::convert::From and std::convert::To traits represent conversions that consume a value of one type, and return a value of another.

From and Into do not borrow; they take ownership of their argument, transform it, and then return ownership of the result back to the caller:


#![allow(unused)]
fn main() {
trait Into<T>: Sized {
    fn into(self) -> T;
}

trait From<T>: Sized {
    fn from(T) -> Self;
}
}

Use Case: Into is generally used to make functions more flexible in the arguments they accept. For example, in this code, the ping function can accept any type A that can be converted into an Ipv4Addr:


#![allow(unused)]
fn main() {
fn ping<A>(address: A) -> std::io::Result<bool>
    where A: Into<Ipv4Addr>
{
    let ipv4_address = address.into();
    ...
}
}

Use Case: The from method of From serves as a generic constructor for producing an instance of a type from some other single value.

Tip: Given an appropriate From implementation, you get the Into trait implementation trait for free!

Warning: from and into operations must be infallible!

ToOwned

The ToOwned trait is an alternative to Clone. Types that implement Clone must be sized. The std::borrow::ToOwned trait provides a slightly looser way to convert a reference to an owned value:


#![allow(unused)]
fn main() {
trait ToOwned {
    type Owned: Borrow<Self>;
    fn to_owned(&self) -> Self::Owned;
}
}

Borrow and ToOwned at Work: The Humble Cow

In some cases, you cannot decide whether to borrow or own a value until the program is running. The std::borrow::Cow type ("clone on write") provides one way to do this:


#![allow(unused)]
fn main() {
enum Cow<'a, B: ?Sized + 'a>
    where B: ToOwned
{
    Borrowed(&'a B),
    Owned(<B as ToOwned>::Owned),
}
}

Concept: A Cow<B> either borrows a shared reference to B, or owns a value from which we could borrow such a reference.

Use Case: One common use for Cow is to return either a statically allocated string constant or a computed string.

Example

Suppose you need to convert an error enum to a message via a function called describe.

Most variants can be handled with fixed strings, but others have additional data that should be included in the message. For such a case, you can return Cow<'static, str>.

Using Cow helps describe and its callers put off allocation until the moment it becomes necessary.


#![allow(unused)]
fn main() {
use std::borrow::Cow;

fn describe(error: &Error) -> Cow<'static, str> {
    match *error {
        Error::OutOfMemory => "out of memory".into(),
        Error::StackOverflow => "stack overflow".into(),
        Error::MachineOnFire => "machine on fire".into(),
        Error::Unfathomable => "machine bewildered".into(),
        Error::FileNotFound(ref path) => {
            format!("file not found: {}", path.display()).into()
        }
    }
}
}