Traits

Implementing a Trait for a Struct

  • The functions inside the Trait may have no implementation or a default implementation that can be overridden by the implementing type.

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

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

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

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

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

Traits as types and Trait Bounds

  • Ex1 : "Accepts anything that implements Summary".

    // Syntax sugar for the version below.
    pub fn notify(item: &impl Summary) {
        println!("Breaking news! {}", item.summarize());
    }
    
    // Same thing, represented via a "trait bound".
    pub fn notify<T: Summary>(item: &T) {
        println!("Breaking news! {}", item.summarize());
    }
    
  • Ex2 :

    pub fn notify(item1: &impl Summary, item2: &impl Summary) {
    }
    
    // Same thing, but the "trait bound" infers directly that the types of 'item1' and 'item2' must be the same.
    pub fn notify<T: Summary>(item1: &T, item2: &T) {
    }
    
  • Ex3 :

    pub fn notify(item: &(impl Summary + Display)) {
    }
    
    // Same thing, but using "trait bound".
    pub fn notify<T: Summary + Display>(item: &T) {
    }
    
  • Ex4 : Use of where , with the sole purpose of avoiding a very long signature.

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

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

Differences between Traits and Abstract Classes in C#

  • Multiple inheritance :

    • Traits in Rust allow multiple implementations . A type can implement several traits, while in C#, a class can only directly inherit from one abstract class (but can implement multiple interfaces).

  • No class hierarchy :

    • Rust has no class hierarchy. Traits are independent from each other and are not part of an inheritance structure like abstract classes in C#. This avoids the rigidity of single inheritance found in C#.

  • No state or fields :

    • Traits in Rust cannot contain fields (state) , while abstract classes in C# can. Traits define only behavior, without storing data.

  • Implementing traits for external types :

    • In Rust, you can implement traits for types defined outside your control (provided you defined the trait or the type). This is not allowed with abstract classes in C#.

  • Generics vs. dynamic typing :

    • Traits in Rust often use generics  to determine behavior at compile time. In C#, polymorphism via abstract classes is often based on dynamic typing  at runtime.