What are the Traits?
A trait in Rust is a collection of methods that define what methods a type must have. It allows you to share common behavior between different data types
When a type implements a trait, it agrees to follow the rules written inside that trait by writing the actual code for those methods.
Traits make your code more flexible, modular, and reusable because they allow different types to use the same method names and behaviors, even if they are not related.
Features of Traits In Rust
1) Abstraction: Traits define behavior without specifying implementation. It means Traits describe what a type should do, but not how it should do it.
For example, a trait might say “every shape must have an area() method,” but it doesn’t say how to calculate it; each type (like circle, rectangle) decides that itself.
2) Polymorphism: Traits allow you to write one piece of code that works with many different types, as long as they follow the same trait.
For example, you can write a function that works with any type having a print() method, no matter what the type is.
3) Static Dispatch: Rust decides which trait implementation to use when compiling the code, not while running it. This makes the code faster because there’s no extra checking at runtime.
For example, the compiler already knows which area() function to call for a rectangle or circle before the program runs.
4) Code Reuse: Traits allow you to write common functionality once and share it across many types. For example, if several types need a display() method, you can define it in a trait and just implement it for each type.
How To Define a Trait In Rust
A trait is defined using the trait keyword to create a group of methods (functions) that types can implement.
Example:
trait Greet {
fn greet(&self);
}
- trait Greet defines a trait named Greet.
- Then we write a method inside the function, but we don’t say how it works here.
How To Implement a Trait?
Once we have defined a trait, we must tell Rust how a specific struct will use those methods. This is done with the impl keyword followed by the struct name.
Example:
trait Describable {
fn describe(&self);
}
struct Book {
title: String,
author: String,
}
impl Describable for Book {
fn describe(&self) {
println!("'{}' is written by {}.", self.title, self.author);
}
}
fn main() {
let my_book = Book {
title: String::from("The Rust Guide"),
author: String::from("John Doe"),
};
my_book.describe();
}
Output:
'The Rust Guide' is written by John Doe.
Traits with Default Method Implementations
In Rust, traits can have default method implementations, which means when you define a method inside a trait, you can also give it a ready-to-use body.
If a struct implements that trait but does not provide its own version of the method, it will automatically use the default one.
However, the struct can still override the default behavior by writing its own version of the method.
Code Example:
trait Introduction {
fn introduce(&self) {
println!("Hi, I am a living being!");
}
}
struct Cat;
// We don't override the method, so it uses the default one
impl Introduction for Cat {}
fn main() {
let my_cat = Cat;
my_cat.introduce(); // Default method is used
}
Output:
Hi, I am a living being!
- In this code, we created a trait named Introduction with a method called introduce() and assigned it a default message.
- The struct Cat implements the trait but does not write its own version.
Traits with Multiple Methods
A trait with multiple methods allows you to group related functionalities in one place, and any type implementing that trait must implement all those methods.
Example:
trait Operations {
fn subtract(&self, x: i32, y: i32) -> i32;
fn divide(&self, x: i32, y: i32) -> i32;
}
struct MathTool;
impl Operations for MathTool {
fn subtract(&self, x: i32, y: i32) -> i32 {
x - y
}
fn divide(&self, x: i32, y: i32) -> i32 {
if y != 0 {
x / y
} else {
println!("Cannot divide by zero!");
0
}
}
}
fn main() {
let tool = MathTool;
println!("Subtraction: {}", tool.subtract(20, 8));
println!("Division: {}", tool.divide(20, 4));
}
Output:
Subtraction: 12
Division: 5
- We defined a trait Operations with two methods: subtract and divide.
- The struct MathTool implements the trait and provides logic for both methods.
What Is Trait Bounds In Rust?
Trait bounds are used to restrict generic types so that they must implement a specific trait.
In other words, if you write a function that accepts a generic type T, you can use a trait bound to say: “T must implement this trait otherwise, it can’t be used here.”
Example:
trait Describable {
fn describe(&self);
}
struct Car {
brand: String,
}
struct Laptop {
model: String,
}
impl Describable for Car {
fn describe(&self) {
println!("This is a car made by {}.", self.brand);
}
}
impl Describable for Laptop {
fn describe(&self) {
println!("This is a laptop, model: {}.", self.model);
}
}
// This function can accept ANY type that implements Describable
fn show_details<T: Describable>(item: T) {
item.describe();
}
fn main() {
let my_car = Car { brand: String::from("Tesla") };
let my_laptop = Laptop { model: String::from("MacBook Air") };
show_details(my_car);
show_details(my_laptop);
}
Output:
This is a car made by Tesla.
This is a laptop, model: MacBook Air.
- We created a trait Describable with a method describe().
- Both the Car and Laptop structs implement this trait.
What Is a Trait Objects In Rust?
Trait objects in Rust are a powerful feature that lets you store and use different types that implement the same trait in a single collection or variable.
They’re created using dyn Trait and used with a smart pointer like Box<dyn Trait> or &dyn Trait.
Code Example:
trait Speak {
fn speak(&self);
}
struct Human;
struct Robot;
impl Speak for Human {
fn speak(&self) {
println!("Human says: Hello!");
}
}
impl Speak for Robot {
fn speak(&self) {
println!("Robot says: Beep boop!");
}
}
fn main() {
// Using trait objects to store different types together
let speakers: Vec<Box<dyn Speak>> = vec![
Box::new(Human),
Box::new(Robot),
];
// Each item uses its own implementation at runtime
for speaker in speakers {
speaker.speak();
}
}
Output:
Human says: Hello!
Robot says: Beep boop!
What Is Deriving Traits?
Deriving traits allows the compiler to automatically generate implementations of some commonly used traits for your structs or enums without writing that code manually.
Rust provides many built-in traits, such as:
- Debug – It allows you to print the structure in a developer-friendly format using {:?}.
- Clone – allows you to make a copy of a value.
- PartialEq – It enables == and != comparisons between values.
Example:
#[derive(Debug, Clone, PartialEq)]
struct Book {
title: String,
pages: u32,
}
fn main() {
let book1 = Book {
title: String::from("Rust Made Easy"),
pages: 320,
};
// Debug trait lets us print the struct directly
println!("Book Info: {:?}", book1);
// Clone trait lets us duplicate the struct
let book2 = book1.clone();
// PartialEq lets us compare two instances
if book1 == book2 {
println!("Both books are the same!");
} else {
println!("Books are different.");
}
}
Output:
Book Info: Book { title: "Rust Made Easy", pages: 320 }
Both books are the same!
Learn More About Rust Programming
- What are Strings in Rust?
- What are Lifetimes in Rust?
- What is the Slices in Rust?
- What are the borrowing in Rust?
- What are the Structs in Rust?
Exercise: Create a ShapeInfo Trait and Implement It
Task For You:
- Define a trait called ShapeInfo with two methods:
- area(&self) -> f64 to calculate the area.
- describe(&self) to print a short description.
- Create two structs: Square and Circle.
- Implement the ShapeInfo trait for both structs.
- In main(), create one square and one circle, then call their trait methods.
Example Solution:
trait ShapeInfo {
fn area(&self) -> f64;
fn describe(&self);
}
struct Square {
side: f64,
}
struct Circle {
radius: f64,
}
impl ShapeInfo for Square {
fn area(&self) -> f64 {
self.side * self.side
}
fn describe(&self) {
println!("I am a square with side length {}.", self.side);
}
}
impl ShapeInfo for Circle {
fn area(&self) -> f64 {
3.14 * self.radius * self.radius
}
fn describe(&self) {
println!("I am a circle with radius {}.", self.radius);
}
}
fn main() {
let s = Square { side: 5.0 };
let c = Circle { radius: 3.0 };
s.describe();
println!("My area is: {}", s.area());
c.describe();
println!("My area is: {}", c.area());
}
Output of this code:

- Try to understand this code example by yourself, and write your own logic.

M.Sc. (Information Technology). I explain AI, AGI, Programming and future technologies in simple language. Founder of BoxOfLearn.com.