Pybites Logo Rust Platform

Defining Custom Traits

Medium +3 pts

🎯 In Python, you get polymorphism through duck typing and abstract base classes:

from abc import ABC, abstractmethod

class Describable(ABC):
    @abstractmethod
    def describe(self) -> str:
        ...

    @abstractmethod
    def tag(self) -> str:
        ...

class Book(Describable):
    def __init__(self, title, author):
        self.title = title
        self.author = author

    def describe(self) -> str:
        return f"{self.title} by {self.author}"

    def tag(self) -> str:
        return "book"

This is class-based inheritance: Book is a Describable. In Python, the ABC enforces that subclasses implement the abstract methods. Without ABC, duck typing handles it — if it has describe(), it's describable.

Rust uses traits instead of inheritance. A trait defines behavior that types can implement:

trait Describable {
    fn describe(&self) -> String;
    fn tag(&self) -> &str;
}

The key difference: traits are implemented for types, not inherited by them. Any type can implement any trait — no class hierarchy needed. Book implements Describable not because it inherits from it, but because it provides the required methods.

Implementing a trait

impl Describable for Book {
    fn describe(&self) -> String {
        format!("{} by {}", self.title, self.author)
    }

    fn tag(&self) -> &str {
        "book"
    }
}

impl Trait for Type — you specify which trait is being implemented for which type. Each required method must be provided. The compiler checks this — forget a method and you get a compile error, not a runtime NotImplementedError.

Traits vs Python ABCs

Both define "contracts" that types must fulfill. The differences:

  • No inheritance required. In Python, you class Book(Describable). In Rust, impl Describable for Book — Book doesn't inherit from anything.
  • Implement for external types. You can implement your trait for types from other libraries. In Python, you can't make int inherit from your ABC (without monkey-patching).
  • Compile-time checking. Missing a trait method is a compile error. Python's ABC raises TypeError at instantiation time.

Default methods

Traits can provide default implementations that types can optionally override:

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

    fn preview(&self) -> String {   // default — can be overridden
        format!("Read more: {}", self.summarize())
    }
}

This is like providing a method body in an ABC:

class Summary(ABC):
    @abstractmethod
    def summarize(self) -> str: ...

    def preview(self) -> str:  # default implementation
        return f"Read more: {self.summarize()}"

Trait objects: mixed collections

Python lists can hold any type. In Rust, Vec<T> requires a single type. To hold different types that share a trait, use trait objects with dyn:

let items: Vec<Box<dyn Describable>> = vec![
    Box::new(Book { title: "Dune".into(), author: "Herbert".into() }),
    Box::new(Movie { title: "Arrival".into(), year: 2016 }),
];

for item in &items {
    println!("[{}] {}", item.tag(), item.describe());
}

Box<dyn Describable> means "a heap-allocated value that implements Describable." This is dynamic dispatch — like Python's polymorphism, with a small runtime cost for the indirection.

Login to see the full task and start coding.

This is a premium exercise

Log in to unlock the full exercise and start coding.

Login to access this exercise