Back to series

Traits and generics without tears

Shared behavior in Rust: traits, trait bounds, and when to use generics.

Table of Contents

Rust has no inheritance, so shared behavior comes from traits. Think of a trait as a contract: any type that signs it must provide these methods.

Defining and implementing

trait Summary {
    fn summarize(&self) -> String;
}

struct Release {
    product: String,
    version: String,
}

impl Summary for Release {
    fn summarize(&self) -> String {
        format!("{} {}", self.product, self.version)
    }
}

Using it generically

Trait bounds say “any T that fulfills the contract”:

fn announce(item: &impl Summary) {
    println!("Update! {}", item.summarize());
}

Choosing between them

  • impl Trait in argument position: quick, readable, usually enough
  • Explicit <T: Summary>: needed when you refer to T more than once
  • Trait objects (dyn Summary): one collection holding mixed types, tiny runtime cost

Combined with ownership from part 1, traits and generics cover most of the architecture you’ll express in Rust day to day.