Rust Traits
Very different things can share an ability. A dog, a car alarm and a phone can all make a sound. A circle and a square both have an area. In Rust, a trait describes such an ability: a set of methods that a type promises to have. Any type can sign up for a trait by implementing those methods.
If you know other languages, traits are close to what they call interfaces.
Defining and implementing a trait
trait Speak {
fn sound(&self) -> String;
}
struct Dog;
struct Cat;
impl Speak for Dog {
fn sound(&self) -> String {
String::from("Woof")
}
}
impl Speak for Cat {
fn sound(&self) -> String {
String::from("Meow")
}
}
fn main() {
println!("The dog says {}", Dog.sound());
println!("The cat says {}", Cat.sound());
}The dog says Woof The cat says Meow
The trait lists the method's name and types, without a body. Each impl Speak for ... block then provides the body for one type. struct Dog; is a struct with no fields at all, which is perfectly allowed.
Functions that accept any type with a trait
The real power appears when a function accepts any type that has the trait. impl Speak in a parameter means "anything that can speak":
trait Area {
fn area(&self) -> f64;
fn describe(&self) -> String {
format!("a shape with area {:.1}", self.area())
}
}
struct Circle { radius: f64 }
struct Square { side: f64 }
impl Area for Circle {
fn area(&self) -> f64 { 3.14159 * self.radius * self.radius }
}
impl Area for Square {
fn area(&self) -> f64 { self.side * self.side }
fn describe(&self) -> String {
format!("a square of side {}", self.side)
}
}
fn report(shape: &impl Area) {
println!("This is {}", shape.describe());
}
fn main() {
report(&Circle { radius: 2.0 });
report(&Square { side: 3.0 });
}This is a shape with area 12.6 This is a square of side 3
describe has a body in the trait itself. That is a default method: types get it for free, and may replace it with their own version, as Square does here.
Display: teaching Rust how to print your type
println!("{}", value) works only for types that implement the standard trait std::fmt::Display. Implement it, and your own types print beautifully:
use std::fmt;
struct Temperature {
celsius: f64,
}
impl fmt::Display for Temperature {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:.1}°C", self.celsius)
}
}
fn main() {
let today = Temperature { celsius: 31.25 };
println!("Today it is {}", today);
let text = today.to_string();
println!("As text: {} ({} characters)", text, text.chars().count());
}Today it is 31.2°C As text: 31.2°C (6 characters)
Notice the bonus: implementing Display also gives you to_string() for free. Many traits in Rust work like this, building on each other.
Traits you can derive
Some common traits can be written automatically with #[derive(...)]: Debug for {:?}, Clone for .clone(), PartialEq for ==, and PartialOrd for < and >. Deriving is quick, and you can always write the impl by hand when you need something special.
Practice exercises
- Add a
Cowtype to the first example and make it say "Moo". - Add a
Rectangleto theAreaexample, with its owndescribe. - Implement
Displayfor aPoint { x: i32, y: i32 }so it prints as(3, 4).