🎓 A1Learner
🔍

Rust Derivable Traits

Derivable traits are the standard traits the compiler can write an impl for on request. A #[derive(...)] line makes that request.

Eq is a promise floats cannot make

Eq is PartialEq plus the promise that every value equals itself. A float cannot promise it, so f64 has PartialEq but not Eq — and HashMap keys must be Eq:

println!("nan == nan is {}", f64::NAN == f64::NAN);

Hash lets your type be a key

Derive Hash and your type can be a HashMap key or HashSet member. The derived hash mixes in every field:

#[derive(PartialEq, Eq, Hash)]
struct Point { x: i32, y: i32 }

let mut set = std::collections::HashSet::new();
set.insert(Point { x: 1, y: 2 });
println!("has point = {}", set.contains(&Point { x: 1, y: 2 }));

Ord sorts by fields, in order

Ord is PartialOrd plus a guaranteed order for every pair. It needs Eq and PartialOrd first. BTreeSet values must have it. Structs compare field by field, in the order you wrote them: major before minor here. (The Debug derive is what prints each version.)

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Version { major: u32, minor: u32 }

let mut versions = std::collections::BTreeSet::new();
versions.insert(Version { major: 2, minor: 0 });
versions.insert(Version { major: 1, minor: 9 });
println!("smallest = {:?}", versions.first().unwrap());

Earlier variants count as smaller

On enums, derived ordering follows the order the variants are declared in:

#[derive(PartialEq, PartialOrd)]
enum Size { Small, Medium, Large }

println!("small < large = {}", Size::Small < Size::Large);

Copy refuses a String field

Copy copies stack bits only. No code runs. Every field must be Copy. The type must also be Clone. A String field rules Copy out:

#[derive(Clone, Copy)]
struct Label { text: String }
// error E0204: the trait `Copy` cannot be implemented for this type

Default fills the fields you skip

Default::default() builds the type from each field's default: 0, false, empty text. And ..Default::default() fills just the fields you did not set:

#[derive(Debug, Default)]
struct Alarm { hour: u8, loud: bool }

let a = Alarm { hour: 7, ..Default::default() };
println!("{:?}", a);

Example

#[derive(Debug, Clone, PartialEq, Default)]
struct Settings { volume: u8, muted: bool }
fn main() {
    let a = Settings::default();
    let b = a.clone();
    println!("{:?}", a);
    println!("same = {}", a == b);
    println!("volume = {}", a.volume);
}

Key points

  • PartialEq gives == and !=
  • Values compare field by field
  • On enums, a value equals only the same variant
  • Display is NOT on the menu
  • The compiler cannot guess what a user should see
  • So you write Display by hand
  • Derive eagerly on types you publish
  • The orphan rule says a trait impl must live in one place
  • That place is the crate that owns the type or the trait
  • So a common trait you skip is one your users can never add for you
  • The publishing starter set is Debug, Clone and PartialEq, almost always
  • Add Eq, Hash, Ord and PartialOrd when ordering or key use makes sense
  • Add Default when an obvious empty value exists
  • Deriving means the compiler writes the trait impl from the type's fields
  • The menu of derivable traits is fixed by the standard library.

In this example, one derive line buys Settings four traits. Default builds a from each field's default: volume 0 and muted false. Debug lets {:?} print the whole struct. Clone makes b a full copy of a. PartialEq then compares the copies field by field, so a == b prints same = true. The last line reads volume straight from a: 0, the number default. Four impls, zero written by hand.

Note

Deriving is mechanical, not magic

Each generated impl does exactly the field-by-field job you would write yourself

And it is refused when any field lacks the trait

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

A struct holds a String field. Which trait can it NOT derive?