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
dropmethod of theDroptrait 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 implementCopy.
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
Sizedtrait is as a bound for type variables: a bound likeT: SizedrequiresTto 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
Sizedbound. This mean that when you writestruct S<T>, Rust assumes you meanstruct S<T: Sized>.
Syntax: Since Rust assumes all type parameters have a
Sizedbound, you have to explicitly opt-out of it using the?Sizedsyntax: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_fromwhenever possible.
Tip: If your
Cloneimplementation simply appliescloneto each field of your type, then Rust can implement it for you by adding#[derive(Clone)]above your type definition.
Warning: The
clonemethod of types that implementClonemust 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,Copycan 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
derefcall 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
DerefandDerefMuttraits are designed for implementing smart pointer types likeBox,Rc, andArc, and types that serve as owning versions of something you would frequently use by reference, the wayVec<T>andStringserve as owning versions of[T]and[str].
Anti-Pattern: Do not implement
DerefandDerefMutfor a type just to make theTargettype'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:
- Use the expression
*sto get the value of the current element - Apply methods implemented by the type of the currently pointed to element
- 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:
Defaultis 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. (thinkoptionsobjects 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(..., ¶ms).unwrap(); }
Tip: Rust does not implicitly implement
Defaultfor struct types, but if all of a struct's fields implementDefault, you can implementDefaultfor 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:
AsRefis typically used to make functions more flexible in the argument types they accept. For instance,std::fs::File::openis declared like this:#![allow(unused)] fn main() { fn open<P: AsRef<Path>>(path: P) -> Result<File>; }
Concept: You can think of using
AsRefas 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 givenTcannot violate the type's invariants.
Anti-Pattern: You should avoid defining your own
AsFootraits when you could just implementAsRef<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&Thashes and compares the same way as the value it's borrowed from.Borrowis 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:
Intois generally used to make functions more flexible in the arguments they accept. For example, in this code, thepingfunction can accept any typeAthat can be converted into anIpv4Addr:#![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
frommethod ofFromserves as a generic constructor for producing an instance of a type from some other single value.
Tip: Given an appropriate
Fromimplementation, you get theIntotrait implementation trait for free!
Warning:
fromandintooperations 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 toB, or owns a value from which we could borrow such a reference.
Use Case: One common use for
Cowis 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() } } } }