Defining Custom Traits
🎯 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
intinherit from your ABC (without monkey-patching). - Compile-time checking. Missing a trait method is a compile error. Python's ABC raises
TypeErrorat 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