Skip to content

Rust Common Traits

On the Traits page you wrote your own traits. But the standard library already comes with a set of traits that almost every Rust program uses. They answer simple questions about a type: Can I print it? Can I copy it? Can I compare it? Can I make an empty one?

Think of them like badges on a uniform. Each badge says “this type knows how to do X”. Many of these badges you can get for free with #[derive(...)]. Others you write by hand. This page walks through the ones you’ll meet most often, and ends with a table you can come back to.

Debug lets you print a value with {:?}. It’s meant for you, the developer — for checking values while you build and fix your code. You almost never write it by hand; just derive it. Use {:#?} for a “pretty” version with one field per line, which is easier to read for big structs:

#[derive(Debug)]
struct Book {
title: String,
pages: u32,
}
fn main() {
let book = Book { title: String::from("Dune"), pages: 412 };
println!("{:?}", book);
println!("{:#?}", book);
}

Output:

Book { title: "Dune", pages: 412 }
Book {
title: "Dune",
pages: 412,
}
▶ Try it Yourself

The dbg! macro you may see in other code also uses Debug.

Display is what {} uses. It’s for user-facing text — the message a person reads on the screen. Rust can’t guess what that should look like, so there’s no derive. You write it yourself by implementing fmt::Display:

use std::fmt;
struct Book {
title: String,
author: String,
}
impl fmt::Display for Book {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} by {}", self.title, self.author)
}
}
fn main() {
let book = Book {
title: String::from("Dune"),
author: String::from("Frank Herbert"),
};
println!("{}", book);
let text = book.to_string();
println!("That text has {} characters", text.len());
}

Output:

Dune by Frank Herbert
That text has 21 characters
▶ Try it Yourself

write! works like println!, but writes into the formatter f instead of the screen. A nice bonus: once a type has Display, you get .to_string() for free. You’ll see Display again on the Custom Errors page, where it gives errors a friendly message.

Both make a duplicate of a value, but in different ways.

  • Clone gives you a .clone() method. You call it on purpose, and it can do real work, like copying all the text in a String.
  • Copy makes a duplicate automatically every time you assign or pass the value. It’s only allowed for small, simple types that can be copied bit by bit: numbers, bool, char, and structs made only of those.
#[derive(Debug, Clone, Copy)]
struct Point {
x: i32,
y: i32,
}
#[derive(Debug, Clone)]
struct User {
name: String,
}
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = p1; // Copy: p1 is still usable
println!("{:?} and {:?}", p1, p2);
let u1 = User { name: String::from("Ana") };
let u2 = u1.clone(); // Clone: we ask for a copy
println!("{:?} and {:?}", u1, u2);
}

Output:

Point { x: 1, y: 2 } and Point { x: 1, y: 2 }
User { name: "Ana" } and User { name: "Ana" }
▶ Try it Yourself

Without Copy, let p2 = p1; would move p1, and you couldn’t use it afterwards (see Ownership). Note that Copy always needs Clone too — that’s why you derive both together.

PartialEq lets you use == and !=. When you derive it, two values are equal if all their fields are equal:

#[derive(Debug, PartialEq)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let a = Point { x: 1, y: 2 };
let b = Point { x: 1, y: 2 };
let c = Point { x: 5, y: 0 };
println!("a == b: {}", a == b);
println!("a != c: {}", a != c);
}

Output:

a == b: true
a != c: true
▶ Try it Yourself

So what is Eq? It adds no new methods. It’s a promise: “every value is always equal to itself”. That sounds obvious, but floats break it — in Rust, f64::NAN == f64::NAN is false. So f64 has PartialEq but not Eq. You need Eq (plus Hash) to use your type as a HashMap key or in a HashSet. If your struct has no float fields, derive both.

PartialOrd and Ord: comparing with < and >

Section titled “PartialOrd and Ord: comparing with < and >”

PartialOrd lets you use <, >, <=, and >=. Ord adds a full ordering, which is what .sort(), .max(), and .min() need. When you derive them, Rust compares fields from top to bottom: first field first, and only if those are equal does it look at the next one.

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Version {
major: u32,
minor: u32,
patch: u32,
}
fn main() {
let old = Version { major: 1, minor: 2, patch: 0 };
let new = Version { major: 1, minor: 10, patch: 3 };
println!("old < new: {}", old < new);
let mut versions = vec![
Version { major: 2, minor: 0, patch: 0 },
new,
old,
];
versions.sort();
for v in &versions {
println!("{}.{}.{}", v.major, v.minor, v.patch);
}
}

Output:

old < new: true
1.2.0
1.10.3
2.0.0
▶ Try it Yourself

Because the fields are compared in order, the order you write the fields in matters. Here major is the most important, which is exactly right for version numbers. For enums, variants listed first count as “smaller”. Just like with Eq, floats only get PartialOrd, not Ord — the Sorting page shows how to sort floats anyway.

Default gives your type a ::default() function that builds a “starting” value. When derived, each field gets its own default: 0 for numbers, false for bool, an empty String, an empty Vec. The ..Default::default() syntax lets you set a few fields and fill in the rest:

#[derive(Debug, Default)]
struct Settings {
volume: u32,
muted: bool,
username: String,
}
fn main() {
let basic = Settings::default();
println!("{:?}", basic);
let custom = Settings {
volume: 80,
..Default::default()
};
println!("{:?}", custom);
}

Output:

Settings { volume: 0, muted: false, username: "" }
Settings { volume: 80, muted: false, username: "" }
▶ Try it Yourself

If you want different starting values (say, volume 50), skip the derive and write impl Default for Settings yourself, with a fn default() -> Self that returns the values you want.

From describes how to build one type from another. You’ve used it already: String::from("hi"). When you implement From, Rust gives you Into for free, going the other way:

struct Celsius(f64);
struct Fahrenheit(f64);
impl From<Celsius> for Fahrenheit {
fn from(c: Celsius) -> Self {
Fahrenheit(c.0 * 9.0 / 5.0 + 32.0)
}
}
fn main() {
let boiling = Fahrenheit::from(Celsius(100.0));
println!("Water boils at {}°F", boiling.0);
let body: Fahrenheit = Celsius(37.0).into();
println!("Body temperature is {:.1}°F", body.0);
}

Output:

Water boils at 212°F
Body temperature is 98.6°F
▶ Try it Yourself

With .into(), Rust needs to know the target type, which is why we wrote let body: Fahrenheit.

How ? uses From. This is where From really shines. When the ? operator meets an error, it calls From::from to turn that error into the error type your function returns. So if you implement From for your own error type, ? converts errors for you:

use std::num::ParseIntError;
#[derive(Debug)]
enum AppError {
BadNumber(ParseIntError),
}
impl From<ParseIntError> for AppError {
fn from(e: ParseIntError) -> Self {
AppError::BadNumber(e)
}
}
fn parse_age(text: &str) -> Result<u32, AppError> {
let age: u32 = text.parse()?; // ParseIntError becomes AppError here
Ok(age)
}
fn main() {
println!("{:?}", parse_age("42"));
println!("{:?}", parse_age("abc"));
}

Output:

Ok(42)
Err(BadNumber(ParseIntError { kind: InvalidDigit }))
▶ Try it Yourself

The Custom Errors page builds on this idea in more detail.

In Rust, a + b is really a call to the add method of the std::ops::Add trait. Implement it for your type, and + just works. type Output says what type the addition produces:

use std::ops::Add;
#[derive(Debug, Clone, Copy, PartialEq)]
struct Point {
x: i32,
y: i32,
}
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
Point { x: self.x + other.x, y: self.y + other.y }
}
}
fn main() {
let a = Point { x: 1, y: 2 };
let b = Point { x: 3, y: 4 };
println!("{:?}", a + b);
}

Output:

Point { x: 4, y: 6 }
▶ Try it Yourself

The same idea works for Sub (-), Mul (*), Neg (unary -), and AddAssign (+=). Use this when the meaning is obvious — adding two points or two amounts of money. If a reader would have to guess what + means for your type, a normal method with a clear name is better.

Here is a quick guide for a typical struct:

Trait Derive? What you get
Debug Almost always {:?} and {:#?} printing
Clone Usually .clone()
Copy Only for small types with no String, Vec, or Box fields Automatic copies instead of moves
PartialEq Usually == and !=
Eq When there are no float fields Needed for HashMap keys (with Hash)
Hash When used as a HashMap key or in a HashSet Hashing
PartialOrd, Ord When values have a natural order <, >, .sort(), .max()
Default When an “empty” value makes sense ::default() and ..Default::default()
Display Can’t derive — write it by hand {} printing and .to_string()

A very common starting line is #[derive(Debug, Clone, PartialEq)]. Add more when you need them — the compiler will tell you when one is missing.

Deriving Copy on a struct with a String.

#[derive(Clone, Copy)]
struct User {
name: String,
age: u32,
}
error[E0204]: the trait `Copy` cannot be implemented for this type
--> src/main.rs:2:8
|
1 | #[derive(Clone, Copy)]
| ---- in this derive macro expansion
2 | struct User {
| ^^^^
3 | name: String,
| ------------ this field does not implement `Copy`

A String owns memory on the heap, so it can’t be copied bit by bit. Remove Copy and use .clone() when you need a duplicate.

Deriving Eq on a struct with a float.

#[derive(Debug, PartialEq, Eq)]
struct Product { name: String, price: f64 }
error[E0277]: the trait bound `f64: Eq` is not satisfied
--> src/main.rs:2:32
|
1 | #[derive(Debug, PartialEq, Eq)]
| -- in this derive macro expansion
2 | struct Product { name: String, price: f64 }
| ^^^^^^^^^^ the trait `Eq` is not implemented for `f64`

Keep PartialEq and drop Eq. Or store the price as whole cents in a u64, which is also safer for money.

Make a Task struct with a priority: u8 and a title: String. Derive the traits you need so that you can sort a Vec<Task>, print each task with {:?}, and compare two tasks with ==. Lower numbers are more urgent, so they should come first. Hint: .sort() needs Ord, and Ord needs Eq, PartialEq and PartialOrd too. Put priority first so it’s compared first.

Show solution
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
struct Task {
priority: u8,
title: String,
}
fn main() {
let mut tasks = vec![
Task { priority: 3, title: String::from("Water plants") },
Task { priority: 1, title: String::from("Pay rent") },
Task { priority: 2, title: String::from("Reply to Sam") },
];
let copy = tasks[1].clone();
println!("Same task? {}", copy == tasks[1]);
tasks.sort();
for task in &tasks {
println!("{:?}", task);
}
}

Output:

Same task? true
Task { priority: 1, title: "Pay rent" }
Task { priority: 2, title: "Reply to Sam" }
Task { priority: 3, title: "Water plants" }
▶ Try it Yourself

Make a Money struct that stores whole cents: u64. Implement From<u64> so 350.into() works, Display so it prints like $3.50, and Add so two amounts can be added with +. Hint: dollars are cents / 100 and the rest is cents % 100. Print the rest with {:02} so 5 cents shows as 05. Derive Copy so you can still print the amounts after adding them.

Show solution
use std::fmt;
use std::ops::Add;
#[derive(Debug, Clone, Copy, PartialEq)]
struct Money {
cents: u64,
}
impl From<u64> for Money {
fn from(cents: u64) -> Self {
Money { cents }
}
}
impl fmt::Display for Money {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "${}.{:02}", self.cents / 100, self.cents % 100)
}
}
impl Add for Money {
type Output = Money;
fn add(self, other: Money) -> Money {
Money { cents: self.cents + other.cents }
}
}
fn main() {
let coffee: Money = 350.into();
let cake = Money::from(405);
let total = coffee + cake;
println!("Coffee: {coffee}");
println!("Cake: {cake}");
println!("Total: {total}");
}

Output:

Coffee: $3.50
Cake: $4.05
Total: $7.55
▶ Try it Yourself
  • Debug ({:?}) is for developers and is almost always derived. Display ({}) is for users and is written by hand.
  • Clone copies on request with .clone(). Copy copies automatically, but only for simple types.
  • PartialEq/Eq give you ==. PartialOrd/Ord give you < and sorting, comparing fields from top to bottom.
  • Default builds a starting value; ..Default::default() fills in the fields you didn’t set.
  • Implement From and you get Into for free. The ? operator uses From to convert errors.
  • Implement std::ops::Add (and friends) to make + work with your own types.

Want to go further? Advanced Traits explains associated types like type Output, default type parameters, and how to implement a trait for a type you didn’t write.

Quick check

1. Which trait does {} in println! use?

2. Why can't a struct with a String field derive Copy?

3. When you derive PartialOrd, how are two structs compared?

4. You implemented From<ParseIntError> for AppError. What does that let ? do?

5. Which trait do you implement to make a + b work for your own type?

Score: 0 / 5