Traits

Trait Bounds on Structs and Enums

A struct or enum definition can place trait bounds directly on its generic type parameters (struct SortedVec<T: Ord>), or leave the type definition unconstrained (struct SortedVec<T>) and place trait bounds only on the impl<T: Ord> blocks that require them.

Why Type Definitions Usually Omit Trait Bounds

When you place a trait bound directly on a struct or enum definition, every single impl block, function signature, and type annotation that mentions that type must repeat (or satisfy) that bound—even for operations like len() or is_empty() that never use the trait:

// Bound placed on the struct definition itself:
struct Cache<K: Eq + std::hash::Hash, V> {
    entries: Vec<(K, V)>,
}

// Even `len()`, which never hashes or compares `K`, is forced to repeat `K: Eq + Hash`:
impl<K: Eq + std::hash::Hash, V> Cache<K, V> {
    fn len(&self) -> usize {
        self.entries.len()
    }
}

fn main() {
    let cache: Cache<&str, i32> = Cache {
        entries: vec![("hits", 10)],
    };
    println!("Cache len: {}", cache.len());
}
Cache len: 1

Omitting K: Eq + std::hash::Hash on the impl block when the struct definition requires it fails with error E0277:

// ❌ does not compile
struct Cache<K: Eq, V> {
    entries: Vec<(K, V)>,
}

impl<K, V> Cache<K, V> {
    fn len(&self) -> usize {
        self.entries.len()
    }
}

fn main() {}
error[E0277]: the trait bound `K: Eq` is not satisfied
 --> src/main.rs:6:12
  |
6 | impl<K, V> Cache<K, V> {
  |            ^^^^^^^^^^^ the trait `Eq` is not implemented for `K`
  |
note: required by a bound in `Cache`
 --> src/main.rs:2:17
  |
2 | struct Cache<K: Eq, V> {
  |                 ^^ required by this bound in `Cache`
help: consider restricting type parameter `K` with trait `Eq`
  |
6 | impl<K: std::cmp::Eq, V> Cache<K, V> {
  |       ++++++++++++++

Idiomatic Pattern: Unconstrained Type Definition + Constrained impl Blocks

The standard library (HashMap<K, V>, BTreeSet<T>, Vec<T>) leaves struct and enum definitions unconstrained and attaches trait bounds only to the specific impl blocks that use those traits:

#[derive(Debug)]
struct MinTracker<T> {
    current_min: Option<T>,
}

// Unconstrained impl block: works for ANY type T
impl<T> MinTracker<T> {
    fn new() -> Self {
        Self { current_min: None }
    }

    fn into_inner(self) -> Option<T> {
        self.current_min
    }
}

// Constrained impl block: only requires `T: Ord` when inserting/comparing
impl<T: Ord> MinTracker<T> {
    fn observe(&mut self, candidate: T) {
        match &self.current_min {
            Some(min) if &candidate >= min => {}
            _ => self.current_min = Some(candidate),
        }
    }
}

fn main() {
    let mut tracker = MinTracker::new();
    tracker.observe(40);
    tracker.observe(12);
    tracker.observe(25);

    println!("Minimum observed: {:?}", tracker.into_inner());
}
Minimum observed: Some(12)

Two Legitimate Cases for Trait Bounds on Structs or Enums

There are two specific cases where a struct or enum definition must declare a trait bound on its type parameter:

1. Referencing an Associated Type Inside a Field or Variant

If a field or enum variant stores an associated type such as T::Owned (from ToOwned) or I::Item (from Iterator), the struct or enum definition must bound T so the compiler knows the associated type exists:

use std::fmt::Debug;

#[derive(Debug)]
struct PeekBuffer<I: Iterator> {
    iter: I,
    peeked: Option<I::Item>,
}

impl<I: Iterator> PeekBuffer<I> {
    fn new(mut iter: I) -> Self {
        let peeked = iter.next();
        Self { iter, peeked }
    }
}

fn main() {
    let buf = PeekBuffer::new([10, 20, 30].into_iter());
    println!("First peeked item: {:?}", buf.peeked);
}
First peeked item: Some(10)

2. Implementing Drop That Calls Trait Methods on T

Rust requires any impl<T: Bound> Drop for Struct<T> to have the exact same bounds as the struct definition itself (E0367). If your Drop implementation must call a trait method on T (such as flushing an inner writer), the struct definition itself must declare that bound:

trait Flushable {
    fn flush(&mut self);
}

struct AutoFlush<W: Flushable> {
    writer: W,
}

impl<W: Flushable> Drop for AutoFlush<W> {
    fn drop(&mut self) {
        self.writer.flush();
    }
}

struct BufferLog(Vec<&'static str>);

impl Flushable for BufferLog {
    fn flush(&mut self) {
        println!("Flushing {} buffered log lines on drop", self.0.len());
        self.0.clear();
    }
}

fn main() {
    let _guard = AutoFlush {
        writer: BufferLog(vec!["boot", "ready"]),
    };
}
Flushing 2 buffered log lines on drop