Common Standard Traits
Prerequisites: Traits
The standard library defines a handful of traits that almost every type ends up
implementing. Most can be derived with #[derive(...)], which writes the
obvious implementation for you, as long as every field also implements the
trait.
| Trait | Gives you | Derivable |
|---|---|---|
Debug |
{:?} and {:#?} formatting |
yes |
Clone |
an explicit .clone() copy |
yes |
Copy |
implicit copies instead of moves (needs Clone) |
yes |
PartialEq / Eq |
== and !=; Eq promises x == x is always true |
yes |
PartialOrd / Ord |
<, >, .cmp(), sorting; Ord is a total order |
yes |
Hash |
use as a HashMap key or HashSet member (with Eq) |
yes |
Default |
Type::default(), a "zero" value |
yes |
Display |
{} formatting for users |
no, write it yourself |
From / Into |
conversions between types | no, write it yourself |
A few rules follow from the table:
Copyis only allowed when every field isCopy. A struct holding aStringorVeccan beClonebut notCopy.f32/f64implementPartialEqandPartialOrdbut notEq,OrdorHash, becauseNaN != NaN. Structs containing floats inherit that limit.- Derived
PartialOrd/Ordcompare fields in declaration order, and enum variants in the order they are written. - Implement
From<A> for Band you getInto<B> for Aautomatically, so implementFrom, notInto.
Syntax
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
struct Version {
major: u32,
minor: u32,
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "v{}.{}", self.major, self.minor)
}
}
impl From<(u32, u32)> for Version {
fn from((major, minor): (u32, u32)) -> Self {
Version { major, minor }
}
}
Example
use std::collections::HashSet;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
struct Version {
major: u32,
minor: u32,
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "v{}.{}", self.major, self.minor)
}
}
impl From<(u32, u32)> for Version {
fn from((major, minor): (u32, u32)) -> Self {
Version { major, minor }
}
}
#[derive(Debug, Clone, Default)]
struct Config {
name: String,
retries: u8,
verbose: bool,
}
fn main() {
let a = Version { major: 1, minor: 4 };
let b = a; // Copy: `a` is still usable
println!("{a} == {b}: {}", a == b);
let c: Version = (1, 10).into(); // Into, provided by our From impl
let d = Version::from((0, 9));
println!("{a} < {c}: {}", a < c);
let mut versions = vec![c, a, d, Version::default()];
versions.sort(); // uses Ord
let names: Vec<String> = versions.iter().map(|v| v.to_string()).collect();
println!("sorted: {}", names.join(", "));
println!("max: {}", versions.iter().max().unwrap());
let unique: HashSet<Version> = [a, b, c].into_iter().collect(); // uses Hash + Eq
println!("unique versions: {}", unique.len());
let base = Config { name: String::from("app"), ..Default::default() };
let copy = base.clone(); // Clone: an explicit deep copy
println!("{copy:?}");
println!("{} retries {} times, verbose: {}", base.name, base.retries, base.verbose);
println!("{:#?}", Version { major: 2, minor: 0 });
}
Expected output
v1.4 == v1.4: true
v1.4 < v1.10: true
sorted: v0.0, v0.9, v1.4, v1.10
max: v1.10
unique versions: 2
Config { name: "app", retries: 0, verbose: false }
app retries 0 times, verbose: false
Version {
major: 2,
minor: 0,
}
Note that v1.4 < v1.10 is true because the fields are compared as numbers
(4 < 10), not as text. ..Default::default() fills every field not written out
with its default value.
Common mistakes
- Deriving
Copyon a struct with aStringfield:error[E0204]: the trait `Copy` cannot be implemented for this type. DeriveCloneonly and call.clone()when needed. - Deriving
EqorHashon a struct with anf64field:error[E0277]: the trait bound `f64: Eq` is not satisfied. - Printing a type with
{}that only derivesDebug:error[E0277]: `Config` doesn't implement `std::fmt::Display`— use{:?}or implementDisplay. - Implementing
Intodirectly instead ofFrom. It works but you lose the free reverse direction; the standard advice is to implementFrom. - Assuming derived
Ordsorts by the field you care about. It uses the first field first. Reorder the fields or usesort_by_key.
Practice
Create #[derive(Debug, Clone, Copy, PartialEq)] struct Celsius(f64); and
struct Fahrenheit(f64); with the same derives. Implement From<Celsius> for Fahrenheit (F = C × 9 / 5 + 32) and Display for both so they print like
100.0°C and 212.0°F. Convert 100 °C and -40 °C using .into().
Show solution
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq)]
struct Celsius(f64);
#[derive(Debug, Clone, Copy, PartialEq)]
struct Fahrenheit(f64);
impl From<Celsius> for Fahrenheit {
fn from(c: Celsius) -> Self {
Fahrenheit(c.0 * 9.0 / 5.0 + 32.0)
}
}
impl fmt::Display for Celsius {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.1}°C", self.0)
}
}
impl fmt::Display for Fahrenheit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.1}°F", self.0)
}
}
fn main() {
for c in [Celsius(100.0), Celsius(-40.0)] {
let f: Fahrenheit = c.into();
println!("{c} = {f}");
}
}
100.0°C = 212.0°F
-40.0°C = -40.0°F