Derive and the standard traits

Rust · Practical · 12 min

These traits decide whether your types can be printed, compared, hashed, copied or converted, so every type you write needs the right set.

The standard library defines a set of traits that nearly every type should consider. Most can be generated with `#[derive(...)]`, which implements them field by field (so each field must implement the trait too).

  • Debug: {:?} formatting for developers. Derive it on almost everything.
  • Clone: explicit .clone(). Copy: implicit bitwise copy on assignment; only for small types whose fields are all Copy, and it requires Clone.
  • PartialEq/Eq: ==. Eq promises equality is reflexive, which floats break (NaN != NaN), so f64 is only PartialEq.
  • PartialOrd/Ord: <, sorting, max. Derived ordering compares fields in declaration order, and enum variants in declaration order.
  • Hash: needed for HashMap keys, together with Eq. Keep it consistent with PartialEq: equal values must hash equally.
  • Default: T::default() and ..Default::default() in struct literals. Enums can derive it by marking one variant #[default].

Some must be written by hand:

  • `Display`: user-facing {} formatting. Implement fmt(&self, f: &mut fmt::Formatter) -> fmt::Result using write!. It also gives you .to_string() for free.
  • `From<T>`: infallible conversion. Implementing From<A> for B automatically provides Into<B> for A, so implement From and accept impl Into<B> in APIs when convenient. TryFrom is the fallible version.

Other traits you will see in signatures: AsRef<str> (cheap reference conversion), Deref, Drop (custom cleanup when a value goes out of scope), Iterator and IntoIterator, Send/Sync for threads, and FromStr, which powers str::parse.

Example

use std::fmt;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
enum Level { #[default] Low, Mid, High }

#[derive(Debug, Clone, PartialEq, Default)]
struct Reading { celsius: f64, level: Level }

impl fmt::Display for Reading {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:.1}°C ({:?})", self.celsius, self.level)
    }
}
impl From<f64> for Reading {
    fn from(celsius: f64) -> Self {
        Reading { celsius, level: if celsius > 30.0 { Level::High } else { Level::Mid } }
    }
}

fn main() {
    let r: Reading = 31.5.into();             // Into comes free with From<f64>
    println!("{r} / {r:?}");                  // 31.5°C (High) / Reading { celsius: 31.5, level: High }
    println!("{}", Level::Low < Level::High); // true: variant order
    println!("{}", Reading::default());       // 0.0°C (Low)
}

Remember

  • Derive Debug, Clone, PartialEq (and friends) unless you have a reason not to
  • Copy only for small plain-data types; Eq and Hash must agree with PartialEq
  • Implement Display for user-facing output and From for conversions; you get to_string and Into free

Watch out for

  • Implementing Into directly instead of From, which loses the blanket conversion.

Check yourself

  1. Why can't you derive `Eq` on a struct containing an `f64` field?
  2. You implement `From<Celsius> for Kelvin`. What else do you get automatically?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: doc.rust-lang.org/book/appendix-03-derivable-traits.html