What Are Traits?
In Rust, a trait is a language feature that allows you to define abstract behaviours and methods that other types can implement, making it possible to abstract over behaviour. Traits define shared behaviour that different types can have in common.
To define a trait, we use the trait keyword, followed by the trait's name and a set of method signatures defined within curly braces {}.
Let's look at an example:
trait Speak {
fn speak(&self);
}
In the example above, we've defined a trait Speak that has one method speak. Any type that implements this trait must define this method.
Implementing Traits
Once we've defined a trait, we can implement that trait for any data type. To do this, we use the impl keyword, followed by the trait name for the data type.
Let's implement our Speak trait for a Dog and a Human struct.
struct Dog {
name: String,
}
struct Human {
name: String,
}
impl Speak for Dog {
fn speak(&self) {
println!("{} says: Woof!", self.name);
}
}
impl Speak for Human {
fn speak(&self) {
println!("{} says: Hello!", self.name);
}
}
Here, we have defined two structures Dog and Human both of which have a name field. We then implemented the Speak trait for both structures with their versions of the speak method.
Using Traits
We can now make use of these traits in our functions. Here's an example:
fn make_speak<T: Speak>(t: T) {
t.speak();
}
let dog = Dog { name: String::from("Fido") };
let human = Human { name: String::from("Alice") };
make_speak(dog); // prints "Fido says: Woof!"
make_speak(human); // prints "Alice says: Hello!"
In the above code make_speak is a generic function that takes any type T that implements the Speak trait. We can now pass any type that implements Speak to this function.
Default Implementations
Rust also allows us to provide default implementations for methods in our trait. This means we can let types implementing our trait use the default method or override it with their own.
trait Speak {
fn speak(&self) {
println!("Hello, I can't specify my species yet!");
}
}
impl Speak for Dog {
// We don't provide a `speak` method here, so Dog uses the default.
}
impl Speak for Human {
fn speak(&self) {
println!("{} says: Hello, I am a human!", self.name);
}
}
let dog = Dog { name: String::from("Fido") };
let human = Human { name: String::from("Alice") };
make_speak(dog); // prints "Hello, I can't specify my species yet!"
make_speak(human); // prints "Alice says: Hello, I am a human!"
In this example, Dog uses the default speak method from the Speak trait, but Human provides its implementation.


