You are viewing a free preview of this lesson.
Subscribe to unlock all 10 lessons in this course and every other course on LearningBro.
Traits and generics are Rust's tools for abstraction and code reuse. Traits define shared behaviour (similar to interfaces), and generics allow you to write code that works with multiple types.
A trait defines a set of methods that a type must implement:
trait Summary {
fn summarise(&self) -> String;
}
struct NewsArticle {
title: String,
author: String,
content: String,
}
impl Summary for NewsArticle {
fn summarise(&self) -> String {
format!("{}, by {} — {}", self.title, self.author, &self.content[..50])
}
}
struct Tweet {
username: String,
content: String,
}
impl Summary for Tweet {
fn summarise(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
Traits can provide default method bodies:
trait Summary {
fn summarise_author(&self) -> String;
fn summarise(&self) -> String {
format!("(Read more from {}...)", self.summarise_author())
}
}
Types can override the default or use it as-is.
Accept any type that implements a trait:
fn notify(item: &impl Summary) {
println!("Breaking news: {}", item.summarise());
}
The equivalent, more explicit form:
fn notify<T: Summary>(item: &T) {
println!("Breaking news: {}", item.summarise());
}
fn notify(item: &(impl Summary + std::fmt::Display)) {
println!("{item}");
}
// Or with trait bound syntax:
fn notify<T: Summary + std::fmt::Display>(item: &T) {
println!("{item}");
}
For complex bounds, use where for readability:
fn some_function<T, U>(t: &T, u: &U) -> String
where
T: Summary + Clone,
U: std::fmt::Display + std::fmt::Debug,
{
format!("{} — {u}", t.summarise())
}
fn create_summarisable() -> impl Summary {
Tweet {
username: String::from("bot"),
content: String::from("Hello from Rust!"),
}
}
Tip:
impl Traitin return position means the function returns exactly one concrete type. You cannot return different types conditionally — for that, use trait objects (Box<dyn Trait>).
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in &list[1..] {
if item > largest {
largest = item;
}
}
largest
}
let numbers = vec![34, 50, 25, 100, 65];
let result = largest(&numbers); // 100
let chars = vec!['y', 'm', 'a', 'q'];
let result = largest(&chars); // 'y'
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
// Implement methods only for specific types
impl Point<f64> {
fn distance_from_origin(&self) -> f64 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
let integer_point = Point { x: 5, y: 10 };
let float_point = Point { x: 1.0, y: 4.0 };
Subscribe to continue reading
Get full access to this lesson and all 10 lessons in this course.