Automatic Trait Derivation

Derive Common Traits Automatically

std

Most structs and enums very commonly implement a number of basic traits - for example, to implement equality, cloning, enable storage in a hashmap, and provide debug formatting.

Instead of manually writing large amounts of repetitive code, you can apply the derive⮳ attribute to struct or enum definitions. The attribute, written #[derive(<Trait_1>, <Trait_2>, ...)], where <Trait_n> should be replaced by a suitable trait name, automagically generates a basic implementation of the selected traits for the type you've annotated.

As described by Derivable traits⮳, the following traits are derivable "out of the box":

  • Comparison traits Eq, PartialEq, Ord, and PartialOrd,
  • Clone, which explicitly creates a deep copy of a value,
  • Copy, which gives a type 'copy semantics' instead of 'move semantics' (less common),
  • Hash to compute a hash of the item,
  • Default to provide a default instance of the type,
  • Debug to format the type for debug purposes.

The PartialEq trait allows checking for equality and enables use of the == and != operators. The Eq trait signals that for every value of a type, the value is equal to itself. The PartialOrd trait allows comparisons for sorting purposes. The Ord trait guarantees that, for any two values of a type, a valid ordering will exist. Certain primitive types, namely floats, implement PartialEq and PartialOrd but cannot implement Eq and Ord.

The Copy trait marks types that can be duplicated (cloned) by solely copying bits stored on the stack.

The Debug trait enables debug formatting, which you request by adding :? within {} placeholders in format strings.

The Default trait provides the default value - the equivalent of an empty string or zero - for your type.

The Hash trait takes an instance of a type of arbitrary size and map that instance to a value of fixed size (a hash code, usually an integer) using a hash function. It is commonly required to use a type as a key in a HashMap or HashSet.

Note that the above traits can still be manually implemented, if a more complex behavior than what derive provides is required.

use std::collections::HashMap;

/// `derive` is a powerful tool in Rust that allows you to automatically
/// implement certain traits for your structs and enums.
///
/// Here we derive several common traits for a simple struct `S` that wraps an
/// `i32`.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Default)]
struct S(i32);

fn simple_tests() {
    let zero = S::default(); // Courtesy of `Default`.
    println!("Debug formatting: {:?}", zero); // Courtesy of `Debug`.
    dbg!(&zero);
    let one = S(1);
    let also_one = one; // Courtesy of `Clone`, `Copy`.
    println!("Equality is implemented. S(1) == S(1): {}", one == also_one); // Courtesy of `PartialEq`, `Eq`.
    println!("Ordering is implemented. S(1) < S(2): {}", one < S(2)); // Courtesy of `PartialOrd`, `Ord`.
}

// The `Hash` trait enables types to be hashed, which means they can
// be used as keys in hash-based collections like `HashMap` and `HashSet`.
// When you use `#[derive(Hash)]` on a struct, the derived implementation
// will typically hash each field of the struct in sequence and combine these
// hashes  into a single hash value for the entire struct instance.
fn hash() {
    let mut map = HashMap::new();

    let s1 = S(10);
    let s2 = S(20);

    // Use instances of `S` as keys:
    map.insert(s1, "Value for S(10)");
    map.insert(s2, "Value for S(20)");

    // Retrieve a value using an `S` key:
    if let Some(value) = map.get(&S(10)) {
        println!("Found: {}", value); // Output: Found: Value for S(10).
    }
}

fn main() {
    simple_tests();
    hash();
}

Install and use the cargo-expand utility to see the exact code that is generated by derive for your specific type.

Derive Additional Traits with derive_more

derive_more

The derive_more⮳ crate derive lots of additional, commonly used traits and static methods for both structs and enums:

  • Arithmetic Traits: Add, Sub, Mul, Div, AddAssign, etc. for custom numeric types,
  • Conversion Traits: From, Into, TryFrom, TryInto for easy type conversions,
  • Smart Pointer Traits: Deref, DerefMut for implementing container types,
  • Display and Error for better formatting and error handling,
  • Constructor to auto-generate constructors for structs,
  • Boolean Operators Not, BitAnd, BitOr, etc.

You can also implement derive for your own traits through procedural macros.

//! The `derive_more` crate extends Rust's built-in derive functionality to
//! provide more automatic implementations for common traits.
//!
//! Add to your `Cargo.toml`:
//! ```toml
//! [dependencies]
//! derive_more = "2.0.1" # Or latest
//! ```

use derive_more::Add;
use derive_more::AddAssign;
use derive_more::Constructor;
use derive_more::Deref;
use derive_more::DerefMut;
use derive_more::Display;
use derive_more::From;
use derive_more::Into;
use derive_more::Sub;
use derive_more::SubAssign;

/// Basic numeric type with arithmetic operations.
#[derive(Add, AddAssign, PartialEq, Debug)]
struct Point {
    x: i32,
    y: i32,
}

/// Newtype pattern with conversion traits.
#[derive(From, Into, Display, Debug)]
struct UserId(u64);

/// Struct with a constructor.
#[derive(Constructor, Debug)]
struct User {
    id: UserId,
    name: String,
    active: bool,
}

// Using `Deref` and `DerefMut`.
#[derive(Deref, DerefMut, Debug)]
struct Stack<T> {
    #[deref]
    #[deref_mut]
    items: Vec<T>,
}

fn main() {
    // Using `Add` and `AddAssign`.
    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    let p3 = p1 + p2; // Thanks to `Add`.
    assert_eq!(p3, Point { x: 4, y: 6 });

    let mut p4 = Point { x: 5, y: 6 };
    p4 += Point { x: 1, y: 1 }; // Thanks to `AddAssign`.
    assert_eq!(p4, Point { x: 6, y: 7 });

    // Using `From` and `Into`.
    let user_id = UserId::from(12345);
    let raw_id: u64 = user_id.into();
    assert_eq!(raw_id, 12345);

    // Using `Display`.
    println!("User ID: {}", UserId(67890)); // Prints "User ID: 67890".

    // Using `Constructor`.
    let user = User::new(UserId(12345), "Alice".to_string(), true);
    println!("{:?}", user); // User { id: UserId(12345), name: "Alice", active: true }.

    // Using `Deref` and `DerefMut`.
    let mut stack = Stack {
        items: vec![1, 2, 3],
    };
    stack.push(4); // Using `Vec`'s push method through `DerefMut`.
    assert_eq!(stack.len(), 4); // Using `Vec`'s len method through `Deref`.
}

// More complex example: Multiple derives on a single type.
#[derive(
    Add, AddAssign, Sub, SubAssign, From, Into, Display, Debug, Clone, Copy,
)]
struct Amount(f64);

// Custom error type with derive_more's `Error` trait.
// `std::error::Error` requires `std::fmt::Debug` and `std::fmt::Display`.
#[derive(Debug, derive_more::Error, Display, From)]
enum AppError {
    #[display("Database error")]
    DatabaseError,

    #[display("Insufficient funds")]
    InsufficientFundsError,
}

// Using multiple types with `derive_more`.
fn transfer_amount(
    from: &mut Amount,
    to: &mut Amount,
    value: Amount,
) -> Result<(), AppError> {
    if from.0 < value.0 {
        return Err(AppError::InsufficientFundsError);
    }

    *from -= value;
    *to += value;

    Ok(())
}

References

Related Topics

  • Attributes.
  • Rust Patterns.