🎓 A1Learner
🔍

Rust Generic Traits

A generic trait is a trait with a type parameter. Record<i32> and Record<String> are two different traits.

The letter is part of the trait name

Write the letter in the trait name. Then Record<i32> and Record<String> are two different traits, and the method signatures use T directly:

trait Record<T> {
    fn record(&mut self, item: T);
}

One type may implement it twice

Each impl fills T in. Both of these live on Log, and both are allowed:

impl Record<String> for Log {
    fn record(&mut self, item: String) { self.entries.push(item); }
}
impl Record<i32> for Log {
    fn record(&mut self, item: i32) { self.entries.push(item.to_string()); }
}

The argument picks the impl

record(42) uses the i32 impl, and record(text) uses the String impl — the argument decides:

let mut log = Log { entries: Vec::new() };
log.record(String::from("booted"));
log.record(7);
println!("entries = {:?}", log.entries);

Example

trait Record<T> {
    fn record(&mut self, item: T);
}
struct Log {
    entries: Vec<String>,
}
impl Record<String> for Log {
    fn record(&mut self, item: String) {
        self.entries.push(item);
    }
}
impl Record<i32> for Log {
    fn record(&mut self, item: i32) {
        self.entries.push(item.to_string());
    }
}
fn main() {
    let mut log = Log { entries: Vec::new() };
    log.record(String::from("started"));
    log.record(42);
    println!("entries = {:?}", log.entries);
}

Key points

  • Use a generic trait when one type may implement it in several ways.
  • There is one way per letter.
  • The standard From<T> trait works exactly this way.
  • An associated type is filled in exactly once per type.
  • So it fits when there is only one sensible choice.
  • A generic trait names a family of traits, one for each type in its parameter.

In this example, Record is generic over the type it records. Log implements it twice. Once for String, pushing the text as it is. And once for i32, turning the number into text first. In main, the first record call passes a String, so the String impl runs. The second passes 42, so the i32 impl runs. The printed list holds both entries as text: ["started", "42"]. One type, one trait family, two impls — chosen by the argument.

Note

A generic trait name alone is incomplete.

Record says a family.

Record<i32> names one member of it.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

How can Log implement Record twice without a clash?