Traits

Using traits to define shared behavior.

Intermediate⏱️ 45 min📚 Prerequisites: 1

Traits

Traits allow us to define shared behavior that different types can implement.

Trait Definition

RUST
trait Summary {
    fn summarize(&self) -> String;
}

Trait Implementation

RUST
struct NewsArticle {
    headline: String,
    location: String,
    author: String,
    content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

Default Implementation

RUST
trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

Trait Parameters

RUST
fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

Trait Bound Syntax

RUST
fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

Multiple Trait Bounds

RUST
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
    // ...
}

where Clause

RUST
fn some_function<T, U>(t: &T, u: &U) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{
    // ...
}

Code Examples

Basic Trait

Trait definition and implementation

RUST
trait Summary {
    fn summarize(&self) -> String;
}
struct NewsArticle {
    headline: String,
    author: String,
}
impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {}", self.headline, self.author)
    }
}
fn main() {
    let article = NewsArticle {
        headline: String::from("Rust new version"),
        author: String::from("John"),
    };
    println!("{}", article.summarize());
}

Explanation:

A trait defines shared behavior. impl Summary for NewsArticle implements the trait for the NewsArticle type. Now NewsArticle can use the summarize() method.

Default Implementation

Trait with default methods

RUST
trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
    fn summarize_author(&self) -> String;
}
struct Tweet {
    username: String,
    content: String,
}
impl Summary for Tweet {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
}
fn main() {
    let tweet = Tweet {
        username: String::from("rust_lang"),
        content: String::from("Rust 1.70 released!"),
    };
    println!("{}", tweet.summarize());
    println!("{}", tweet.summarize_author());
}

Explanation:

Trait methods can have default implementations. If a type doesn't implement it explicitly, it uses the default version.

Trait Parameters

Functions with trait parameters

RUST
trait Summary {
    fn summarize(&self) -> String;
}
struct NewsArticle {
    headline: String,
}
impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        self.headline.clone()
    }
}
fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}
fn notify2<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}
fn main() {
    let article = NewsArticle {
        headline: String::from("Rust news"),
    };
    notify(&article);
    notify2(&article);
}

Explanation:

The &impl Summary parameter accepts any type that implements the Summary trait. This enables polymorphism.

Blockchain: Validatable Trait

Using traits for blockchain validation

RUST
trait Validatable {
    fn validate(&self) -> bool;
    fn get_id(&self) -> String;
}
struct Transaction {
    id: String,
    amount: u64,
    fee: u64,
}
struct Block {
    id: String,
    previous_hash: String,
    hash: String,
}
impl Validatable for Transaction {
    fn validate(&self) -> bool {
        self.amount > 0 && self.fee >= 10
    }
    fn get_id(&self) -> String {
        self.id.clone()
    }
}
impl Validatable for Block {
    fn validate(&self) -> bool {
        !self.hash.is_empty() && !self.previous_hash.is_empty()
    }
    fn get_id(&self) -> String {
        self.id.clone()
    }
}
fn process_validatable<T: Validatable>(item: &T) {
    if item.validate() {
        println!("{} is valid", item.get_id());
    } else {
        println!("{} is invalid", item.get_id());
    }
}
fn main() {
    let tx = Transaction {
        id: String::from("tx1"),
        amount: 100,
        fee: 10,
    };
    let block = Block {
        id: String::from("block1"),
        previous_hash: String::from("prev"),
        hash: String::from("hash"),
    };
    process_validatable(&tx);
    process_validatable(&block);
}

Explanation:

Traits enable polymorphism in blockchain code. Different types (transactions, blocks) can implement the same trait, allowing generic validation functions. This is essential for blockchain architecture.

Exercises

Trait Implementation

Create a Drawable trait and implement it on a struct!

Medium

Starter Code:

RUST
fn main() {
    let circle = Circle { radius: 5.0 };
    circle.draw();
}