Rust Traits and Generics

Example:

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

struct NewsArticle {
    headline: String,
    content: String,
}

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

fn main() {
    let article = NewsArticle {
        headline: String::from("Rust Programming Language"),
        content: String::from("Rust is a systems programming language."),
    };
    
    println!("{}", article.summarize());
}
                    
                  
  • Defining a Trait: Use the trait keyword followed by the trait name and method signatures.
  • Implementing a Trait: Use the impl keyword for the type and specify the trait.

Default Implementations:

Traits can provide default method implementations.

Example:

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

struct NewsArticle {
    headline: String,
    content: String,
}

impl Summary for NewsArticle {
    // Using the default implementation
}

fn main() {
    let article = NewsArticle {
        headline: String::from("Rust Programming Language"),
        content: String::from("Rust is a systems programming language."),
    };
    
    println!("{}", article.summarize());
}
                    
                  

Using Generics to Write Reusable Code

Generic enables you to write the code that is flexible and reusable. They enable you to write functions, structs, enums, and traits that can operate on multiple types of data.

Generic Functions:

Example:

                    
fn largest(list: &[T]) -> &T {
    let mut largest = &list[0];
    for item in list.iter() {
        if item > largest {
            largest = item;
        }
    }
    largest
}

fn main() {
    let numbers = vec![34, 50, 25, 100, 65];
    let result = largest(&numbers);
    println!("The largest number is {}", result);

    let chars = vec!['y', 'm', 'a', 'q'];
    let result = largest(&chars);
    println!("The largest char is {}", result);
}
                    
                  

  • Function Definitions: Specify a generic type parameter inside angle brackets ().

Generic Structs:

Example:

                    
struct Point<T> {
    x: T,
    y: T,
}

fn main() {
    let integer = Point { x: 5, y: 10 };
    let float = Point { x: 1.0, y: 4.0 };

    println!("Point with integers: ({}, {})", integer.x, integer.y);
    println!("Point with floats: ({}, {})", float.x, float.y);
}
                    
                  

Generic Enums:

Example:

                    
enum Option {
    Some(T),
    None,
}

fn main() {
    let some_number = Option::Some(5);
    let some_string = Option::Some("a string");

    println!("{:?}, {:?}", some_number, some_string);
}

                    
                  

Trait Bounds and Associated Types

Trait Bounds:

Trait bounds require the generic type to implement a given trait. Here, it is necessary to implement the methods specified by trait on the generic type.

Example:

                    
fn largest(list: &[T]) -> T {
    let mut largest = list[0];
    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }
    largest
}

fn main() {
    let numbers = vec![34, 50, 25, 100, 65];
    let result = largest(&numbers);
    println!("The largest number is {}", result);
}
                    
                  

Where Clause:

For readability, especially with multiple trait bounds, you can use a where clause.

Example:

                    
fn largest(list: &[T]) -> T
where
    T: PartialOrd + Copy,
{
    let mut largest = list[0];
    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }
    largest
}
                    
                  

Associated Types:

Associated types are a kind of type alias for traits. This can lead to trait implementations more straightforward and comprehensible.

Example:

                    
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
        }
    }
}

fn main() {
    let mut counter = Counter { count: 0 };

    while let Some(value) = counter.next() {
        println!("{}", value);
    }
}
                    
                  

Summary

  • Traits: Define shared behavior; implemented by types.
  • Generics: Enable writing flexible, reusable code that works with multiple types.
  • Trait Bounds: Constrain generic types to those that implement specific traits.
  • Associated Types: Simplify trait definitions and their implementations by relating traits to types.

These features help you write powerful, flexible, and type-safe Rust code.