derive and marker traits

In Rust the #[derive(...)] attribute asks the compiler to write a trait implementation for a struct or an enum. It is the standard way to get Debug, Clone, PartialEq, Default and Copy without repeating boilerplate, and what it writes is ordinary code that the lesson on operator traits would otherwise have you write by hand.

The lesson covers the four derives a value type usually needs, the marker traits that are not derived at all, and the error for asking for Copy on a type that owns memory. The limit of every derive is the same: it can only do the obvious thing, and anything more interesting is a manual implementation.

Debug

#[derive(Debug)] writes the implementation behind the {:?} placeholder. It prints the type name and each field, which is enough to see what a value contains and is not meant as user-facing output.

The program derives Debug, prints the point in the debug form and also reads its fields, so the fields are used and the build is clean. The printed struct is the whole value.

#[derive(Debug)] struct Point { x: i32, y: i32, } fn main() { let point = Point { x: 1, y: 2 }; println!("{point:?}"); println!("{}", point.x + point.y); }

rustc --edition 2024 debug.rs
./debug

Point { x: 1, y: 2 }
3

The pretty form {:#?} comes from the same implementation, and the lesson on formatting shows how the two differ. A type without the derive is E0277 at the first debug placeholder.

Clone and PartialEq

#[derive(Clone)] clones every field, and #[derive(PartialEq)] compares every field. The two are often derived together for a value type, and both require the field types to implement the trait as well.

The program clones a point, compares it with the original and prints the clone. The comparison is true because clone copied every field.

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

rustc --edition 2024 clone_eq.rs
./clone_eq

true Point { x: 1, y: 2 }

The derives write both methods of the comparison, equality and inequality, from one implementation of eq. A manual PartialEq can compare fewer fields, which the lesson on operator traits does for a version number. The derive is the choice when equality means every field.

Default

#[derive(Default)] fills every field with its default value: zero for numbers, false for booleans, an empty string for a String. It gives the type an associated function, Config::default(), which is the usual starting point for a configuration.

The program builds the default config, prints it and reads both fields. The output shows the zero and the false, which are the defaults of the field types.

#[derive(Default, Debug)] struct Config { retries: u32, verbose: bool, } fn main() { let config = Config::default(); println!("{:?}", config); println!("{} {}", config.retries, config.verbose); }

rustc --edition 2024 default.rs
./default

Config { retries: 0, verbose: false }
0 false

The derive uses the field types, so a field with no Default makes the derive fail. The struct update syntax from the lesson on structs is the other way to start from a full set of values.

Marker traits

Send and Sync are marker traits with no methods, and the compiler implements them automatically for types whose fields are safe to move or share between threads. There is nothing to derive: a wrong implementation would be unsound.

The program requires Send from two ordinary types and prints the size of a cell. A type that contains a non-Send field would fail the same requirement, which the lesson on Send and Sync demonstrates.

use std::cell::Cell; fn require_send<T: Send>() {} fn main() { require_send::<i32>(); require_send::<String>(); println!("{}", std::mem::size_of::<Cell<i32>>()); }

rustc --edition 2024 markers.rs
./markers

4

A marker bound is checked at compile time and adds nothing to the binary, which is why the function body is empty. The auto traits are the reason a Mutex can be shared between threads only when the value inside it is Send, and that rule is also the reason a type containing Rc is rejected by the same function. The cell is four bytes, which is the size of the integer inside it. The point of the section is the function signature: a bound on a marker trait states a property the compiler already knows.

Copy on a type that owns memory

Copy promises that duplication is a plain bit copy, so every field must be Copy. A struct with a String cannot implement it, and the derive fails with E0204, naming the field that broke the promise.

The program asks for Copy and Clone on a struct with a String. The error points at the derive and at the field, which is the shortest explanation of why String is not Copy.

#[derive(Copy, Clone)] struct Config { name: String, } fn main() { let config = Config { name: String::from("dev") }; let copy = config; println!("{}", copy.name); }

rustc --edition 2024 copy_err.rs

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

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0204`.

Removing Copy and keeping Clone is the fix, and the clone allocates. The lesson on Copy and Clone explains the difference in cost.

Exercises

  1. Derive Debug and PartialEq on a struct with two fields and compare two values.
  2. Derive Default and build a configuration, then change one field with the update syntax.
  3. Remove Debug from the first struct and read the error at the debug placeholder.

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. What does the derive attribute do?

    Answer

    It asks the compiler to write a trait implementation

    The derive expands into an ordinary impl block written by the compiler.

  2. Which trait makes {:?} work?

    Answer

    Debug

    The debug placeholder calls Debug; the empty braces call Display.

  3. What does #[derive(Default)] produce?

    Answer

    An associated function default() that fills every field

    Default provides the default value of the type, built from the field defaults.

  4. Why can Send and Sync not be derived in the usual way?

    Answer

    They are marker traits the compiler implements from the fields automatically

    The compiler decides them from the field types, and a wrong implementation would be unsound.

  5. What does this program print? #[derive(Debug)] struct Point { x: i32, y: i32, } fn main() { let point = Point { x: 1, y: 2 }; println!("{point:?}"); }

    Answer

    Point { x: 1, y: 2 }

    The derived implementation prints the type name and each field with its name.

  6. What does this program print? #[derive(Clone, PartialEq, Debug)] struct Point { x: i32, y: i32, } fn main() { let a = Point { x: 1, y: 2 }; let b = a.clone(); println!("{} {:?}", a == b, b); }

    Answer

    true Point { x: 1, y: 2 }

    The clone has the same fields, so the derived equality is true and the debug print shows them.

  7. What does this program print? #[derive(Default, Debug)] struct Config { retries: u32, verbose: bool, } fn main() { let config = Config::default(); println!("{:?}", config); println!("{} {}", config.retries, config.verbose); }

    Answer

    Config { retries: 0, verbose: false } / 0 false

    The defaults are zero and false, and the second line prints them as numbers and a bool.

  8. What does this program print? use std::cell::Cell; fn require_send<T: Send>() {} fn main() { require_send::<i32>(); require_send::<String>(); println!("{}", std::mem::size_of::<Cell<i32>>()); }

    Answer

    4

    Both types are Send, and a Cell around an i32 is four bytes.

  9. What is the first line of the compiler output for this program? #[derive(Copy, Clone)] struct Config { name: String, } fn main() { let config = Config { name: String::from("dev") }; let copy = config; println!("{}", copy.name); }

    Answer

    error[E0204]: the trait Copy cannot be implemented for this type

    The String field is not Copy, and the error names the derive and the field.

  10. What does this program print? #[derive(Debug, PartialEq)] enum Light { Red, Green, } fn main() { println!("{} {:?}", Light::Red == Light::Green, Light::Green); }

    Answer

    false Green

    The two variants differ, and the debug form prints the variant name.

The code on this page was built with rustc 1.96.1 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container.

Article author: Arthur Isaev