Traits

On this page 22

Traits define shared behavior that types can implement. They enable polymorphism, operator overloading, and generic programming.

Defining Traits

Basic Trait Definition

trait Animal {
  fn make_sound(&self): string
  fn get_name(&self): string
}

Implementing Traits

struct Dog {
  name: string
}

struct Cat {
  name: string
}

impl Animal for Dog {
  fn make_sound(&self): string {
    "Woof!"
  }

  fn get_name(&self): string {
    self.name
  }
}

impl Animal for Cat {
  fn make_sound(&self): string {
    "Meow!"
  }

  fn get_name(&self): string {
    self.name
  }
}

Using Traits

fn greet_animal(animal: &dyn Animal) {
  print("{} says {}", animal.get_name(), animal.make_sound())
}

let dog = Dog { name: "Buddy" }
let cat = Cat { name: "Whiskers" }

greet_animal(&dog)  // Buddy says Woof!
greet_animal(&cat)  // Whiskers says Meow!

Default Implementations

Traits can provide default method implementations:

trait Summary {
  fn summarize_author(&self): string

  // Default implementation
  fn summarize(&self): string {
    "Read more from " + self.summarize_author() + "..."
  }
}

struct Article {
  author: string,
  title: string,
  content: string
}

struct Tweet {
  username: string,
  content: string
}

impl Summary for Article {
  fn summarize_author(&self): string {
    self.author
  }

  // Override default
  fn summarize(&self): string {
    self.title + " by " + self.author
  }
}

impl Summary for Tweet {
  fn summarize_author(&self): string {
    "@" + self.username
  }
  // Uses default summarize()
}

Associated Types

Traits can have associated types:

trait Iterator {
  type Item

  fn next(&mut self): Option<Self::Item>
}

struct Counter {
  count: u32,
  max: u32
}

impl Iterator for Counter {
  type Item = u32

  fn next(&mut self): Option<u32> {
    if (self.count < self.max) {
      self.count += 1
      Some(self.count)
    } else {
      None
    }
  }
}

Trait Bounds

Constrain generic types with traits:

Basic Bounds

fn print_summary<T: Summary>(item: &T) {
  print("Summary: {}", item.summarize())
}

Multiple Bounds

fn print_details<T: Summary + Display>(item: &T) {
  print("Display: {}", item)
  print("Summary: {}", item.summarize())
}

Where Clauses

For complex bounds:

fn complex_function<T, U>(t: T, u: U)
where
  T: Clone + Debug,
  U: Clone + Debug
{
  let t_copy = t.clone()
  let u_copy = u.clone()

  print("T: {:?}", t_copy)
  print("U: {:?}", u_copy)
}

Trait Inheritance

Traits can extend other traits:

trait Shape {
  fn area(&self): f64
}

trait Colored {
  fn color(&self): string
}

trait ColoredShape: Shape + Colored {
  fn describe(&self): string {
    "A " + self.color() + " shape with area " + self.area().to_string()
  }
}

struct ColoredCircle {
  radius: f64,
  color: string
}

impl Shape for ColoredCircle {
  fn area(&self): f64 {
    3.14159 _ self.radius _ self.radius
  }
}

impl Colored for ColoredCircle {
  fn color(&self): string {
    self.color
  }
}

impl ColoredShape for ColoredCircle {}

Generic Traits

Traits can be generic:

trait Add<Rhs = Self> {
  type Output

  fn add(self, rhs: Rhs): Self::Output
}

struct Point {
  x: f64,
  y: f64
}

// Point + Point
impl Add for Point {
  type Output = Point

  fn add(self, rhs: Point): Point {
    Point {
      x: self.x + rhs.x,
      y: self.y + rhs.y
    }
  }
}

// Point + f64 (scalar)
impl Add<f64> for Point {
  type Output = Point

  fn add(self, scalar: f64): Point {
    Point {
      x: self.x + scalar,
      y: self.y + scalar
    }
  }
}

Trait Objects

Use dynamic dispatch with trait objects:

trait Drawable {
  fn draw(&self)
}

struct Circle {
  x: f64,
  y: f64,
  radius: f64
}

struct Rectangle {
  x: f64,
  y: f64,
  width: f64,
  height: f64
}

impl Drawable for Circle {
  fn draw(&self) {
    print("Drawing circle at ({}, {}) with radius {}",
          self.x, self.y, self.radius)
  }
}

impl Drawable for Rectangle {
  fn draw(&self) {
    print("Drawing rectangle at ({}, {}) with size {}x{}",
          self.x, self.y, self.width, self.height)
  }
}

fn render_all(shapes: &[&dyn Drawable]) {
  for (shape in shapes) {
    shape.draw()
  }
}

Standard Traits

Clone and Copy

trait Clone {
  fn clone(&self): Self
}

trait Copy: Clone {}  // Marker trait

# [derive(Clone, Copy)]
struct Point {
  x: i32,
  y: i32
}

Debug and Display

trait Display {
  fn fmt(&self): string
}

trait Debug {
  fn debug_fmt(&self): string
}

impl Display for Point {
  fn fmt(&self): string {
    "({}, {})".format(self.x, self.y)
  }
}

impl Debug for Point {
  fn debug_fmt(&self): string {
    "Point { x: {}, y: {} }".format(self.x, self.y)
  }
}

From and Into

trait From<T> {
  fn from(value: T): Self
}

trait Into<T> {
  fn into(self): T
}

// Blanket implementation
impl<T, U> Into<U> for T where U: From<T> {
  fn into(self): U {
    U.from(self)
  }
}

struct Celsius(f64)
struct Fahrenheit(f64)

impl From<Fahrenheit> for Celsius {
  fn from(f: Fahrenheit): Celsius {
    Celsius((f.0 - 32.0) * 5.0 / 9.0)
  }
}

let f = Fahrenheit(98.6)
let c: Celsius = f.into()  // Uses Into trait

Eq and Ord

trait PartialEq {
  fn eq(&self, other: &Self): bool

  fn ne(&self, other: &Self): bool {
    !self.eq(other)
  }
}

trait Eq: PartialEq {}

trait PartialOrd: PartialEq {
  fn partial_cmp(&self, other: &Self): Option<Ordering>
}

trait Ord: Eq + PartialOrd {
  fn cmp(&self, other: &Self): Ordering
}

Derive Macros

Automatically implement common traits:

# [derive(Debug, Clone, PartialEq, Eq)]
struct User {
  id: i64,
  name: string,
  email: string
}

let user1 = User { id: 1, name: "Alice", email: "alice@example.com" }
let user2 = user1.clone()

print("{:?}", user1)           // Debug output
print("Equal: {}", user1 == user2)  // true

Builder Pattern

Using traits for builders:

trait Builder {
  type Output

  fn build(self): Self::Output
}

struct UserBuilder {
  name: Option<string>,
  email: Option<string>,
  age: Option<u32>
}

impl UserBuilder {
  fn new(): UserBuilder {
    UserBuilder { name: None, email: None, age: None }
  }

  fn name(mut self, name: string): UserBuilder {
    self.name = Some(name)
    self
  }

  fn email(mut self, email: string): UserBuilder {
    self.email = Some(email)
    self
  }

  fn age(mut self, age: u32): UserBuilder {
    self.age = Some(age)
    self
  }
}

impl Builder for UserBuilder {
  type Output = Result<User, string>

  fn build(self): Result<User, string> {
    let name = self.name.ok_or("Name is required")?
    let email = self.email.ok_or("Email is required")?
    let age = self.age.unwrap_or(0)

    Ok(User { name, email, age })
  }
}

let user = UserBuilder.new()
  .name("Alice")
  .email("alice@example.com")
  .age(30)
  .build()
  .unwrap()

Repository Pattern

trait Repository<T> {
  fn find_by_id(&self, id: u64): Option<T>
  fn save(&mut self, entity: T): Result<(), string>
  fn delete(&mut self, id: u64): Result<(), string>
  fn find_all(&self): Vec<T>
}

struct InMemoryRepository<T> {
  data: HashMap<u64, T>,
  next_id: u64
}

impl<T: Clone> Repository<T> for InMemoryRepository<T> {
  fn find_by_id(&self, id: u64): Option<T> {
    self.data.get(&id).cloned()
  }

  fn save(&mut self, entity: T): Result<(), string> {
    let id = self.next_id
    self.next_id += 1
    self.data.insert(id, entity)
    Ok(())
  }

  fn delete(&mut self, id: u64): Result<(), string> {
    self.data.remove(&id)
      .map(|_| ())
      .ok_or("Entity not found")
  }

  fn find_all(&self): Vec<T> {
    self.data.values().cloned().collect()
  }
}

Next Steps

Released under the MIT License.