Advanced Level
Advanced Traits
Chapter 18: Advanced Traits š
Explore sophisticated trait features like associated types, default generic types, fully qualified syntax, and supertraits.
Associated Types
Defining Associated Types
Associated types allow traits to define placeholders for types that are filled in by implementors:
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
struct Counter {
count: u32,
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
self.count += 1;
if self.count < 6 {
Some(self.count)
} else {
None
}
}
}Associated Types vs Generic Types
Without associated types (verbose):
trait IteratorGeneric<T> {
fn next(&mut self) -> Option<T>;
}With associated types (cleaner):
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}Default Generic Type Parameters
Operator Overloading
use std::ops::Add;
#[derive(Debug, PartialEq)]
struct Point {
x: i32,
y: i32,
}
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
Point {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
fn main() {
assert_eq!(
Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
Point { x: 3, y: 3 }
);
}Custom Add Implementation with Default Type Parameter
use std::ops::Add;
trait Adder<Rhs = Self> {
type Output;
fn add(self, rhs: Rhs) -> Self::Output;
}
#[derive(Debug, Clone, Copy)]
struct Millimeters(u32);
#[derive(Debug, Clone, Copy)]
struct Meters(u32);
impl Add<Meters> for Millimeters {
type Output = Millimeters;
fn add(self, other: Meters) -> Millimeters {
Millimeters(self.0 + (other.0 * 1000))
}
}
impl Add for Millimeters {
type Output = Millimeters;
fn add(self, other: Millimeters) -> Millimeters {
Millimeters(self.0 + other.0)
}
}Fully Qualified Syntax for Disambiguation
Calling Methods with Same Name
trait Pilot {
fn fly(&self);
}
trait Wizard {
fn fly(&self);
}
struct Human;
impl Pilot for Human {
fn fly(&self) {
println!("This is your captain speaking.");
}
}
impl Wizard for Human {
fn fly(&self) {
println!("Up!");
}
}
impl Human {
fn fly(&self) {
println!("*waving arms furiously*");
}
}
fn main() {
let person = Human;
// Calls the inherent method
person.fly();
// Fully qualified syntax to disambiguate
Pilot::fly(&person);
Wizard::fly(&person);
// Alternative syntax using <Type as Trait>::method
<Human as Pilot>::fly(&person);
<Human as Wizard>::fly(&person);
}Supertraits
Requiring One Trait in Another
use std::fmt;
trait OutlinePrint: fmt::Display {
fn outline_print(&self) {
let output = self.to_string();
let len = output.len();
println!("{}", "*".repeat(len + 4));
println!("* {} *", output);
println!("{}", "*".repeat(len + 4));
}
}
struct Point {
x: i32,
y: i32,
}
impl OutlinePrint for Point {} // Error: Point doesn't implement Display
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
impl OutlinePrint for Point {} // Now it works!
fn main() {
let point = Point { x: 1, y: 2 };
point.outline_print();
}Newtype Pattern for Type Safety
Wrapper Types
use std::fmt;
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}
fn main() {
let w = Wrapper(vec![
String::from("hello"),
String::from("world"),
]);
println!("w = {}", w);
}Practical Examples
Example 1: Custom Database Connection Trait
use std::fmt::Display;
// Supertrait that requires Display
trait DatabaseConnection: Display {
type Error: std::error::Error;
type Row;
fn connect(url: &str) -> Result<Self, Self::Error>
where
Self: Sized;
fn execute_query(&self, query: &str) -> Result<Vec<Self::Row>, Self::Error>;
fn close(&mut self);
}
#[derive(Debug)]
struct PostgresConnection {
url: String,
connected: bool,
}
#[derive(Debug)]
struct PostgresError {
message: String,
}
impl std::error::Error for PostgresError {}
impl Display for PostgresError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "PostgreSQL Error: {}", self.message)
}
}
impl Display for PostgresConnection {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "PostgreSQL Connection to {}", self.url)
}
}
impl DatabaseConnection for PostgresConnection {
type Error = PostgresError;
type Row = String;
fn connect(url: &str) -> Result<Self, Self::Error> {
// Simulate connection logic
if url.is_empty() {
Err(PostgresError {
message: String::from("URL cannot be empty"),
})
} else {
Ok(PostgresConnection {
url: url.to_string(),
connected: true,
})
}
}
fn execute_query(&self, query: &str) -> Result<Vec<Self::Row>, Self::Error> {
if !self.connected {
return Err(PostgresError {
message: String::from("Not connected to database"),
});
}
if query.is_empty() {
return Err(PostgresError {
message: String::from("Query cannot be empty"),
});
}
// Simulate query execution
Ok(vec![
String::from("row1"),
String::from("row2"),
String::from("row3"),
])
}
fn close(&mut self) {
self.connected = false;
println!("PostgreSQL connection closed");
}
}
fn main() {
match PostgresConnection::connect("postgresql://localhost:5432/mydb") {
Ok(conn) => {
println!("Connected: {}", conn);
match conn.execute_query("SELECT * FROM users") {
Ok(rows) => {
for row in rows {
println!("Row: {}", row);
}
}
Err(e) => println!("Error: {}", e),
}
}
Err(e) => println!("Connection failed: {}", e),
}
}Example 2: Advanced Iterator Trait Implementation
trait AdvancedIterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
fn size_hint(&self) -> (usize, Option<usize>) {
(0, None)
}
fn count(self) -> usize
where
Self: Sized,
{
let mut count = 0;
let mut iter = self;
while let Some(_) = iter.next() {
count += 1;
}
count
}
fn map<B, F>(self, f: F) -> Map<Self, F>
where
Self: Sized,
F: FnMut(Self::Item) -> B,
{
Map { iter: self, f }
}
}
struct Map<I, F> {
iter: I,
f: F,
}
impl<B, I, F> AdvancedIterator for Map<I, F>
where
I: AdvancedIterator,
F: FnMut(I::Item) -> B,
{
type Item = B;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(&mut self.f)
}
}
struct Counter {
count: u32,
max: u32,
}
impl Counter {
fn new(max: u32) -> Counter {
Counter { count: 0, max }
}
}
impl AdvancedIterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.count < self.max {
self.count += 1;
Some(self.count)
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = (self.max - self.count) as usize;
(remaining, Some(remaining))
}
}
fn main() {
let counter = Counter::new(5);
println!("Count: {}", counter.count());
let counter = Counter::new(3);
let doubled: Vec<u32> = counter.map(|x| x * 2).collect();
println!("Doubled: {:?}", doubled);
}Common Mistakes
ā Forgetting Supertrait Requirements
trait OutlinePrint {
fn outline_print(&self) {
let output = self.to_string(); // Error: to_string() not available
// ...
}
}ā Properly Implementing Supertraits
use std::fmt::Display;
trait OutlinePrint: Display {
fn outline_print(&self) {
let output = self.to_string(); // Now works because Display is required
// ...
}
}ā Incorrect Associated Type Implementation
trait Container {
type Item;
fn insert(&mut self, item: Self::Item);
fn remove(&mut self) -> Option<Self::Item>;
}
struct MyContainer {
items: Vec<String>,
}
impl Container for MyContainer {
type Item = i32; // Mismatch with Vec<String>
fn insert(&mut self, item: Self::Item) {
// Error: trying to insert i32 into Vec<String>
self.items.push(item);
}
fn remove(&mut self) -> Option<Self::Item> {
// Error: trying to return String as i32
self.items.pop()
}
}ā Correct Associated Type Implementation
impl Container for MyContainer {
type Item = String;
fn insert(&mut self, item: Self::Item) {
self.items.push(item);
}
fn remove(&mut self) -> Option<Self::Item> {
self.items.pop()
}
}Key Takeaways
- ā Associated types allow traits to define placeholders for types
- ā Default generic type parameters make traits more flexible
- ā Fully qualified syntax resolves method name ambiguities
- ā Supertraits require other traits to be implemented first
- ā The newtype pattern provides type safety and encapsulation
- ā Advanced traits enable powerful abstractions in Rust
- ā Follow Rust naming conventions and idioms for trait usage
Ready for Chapter 19? ā Unsafe Rust