Rust Generics

Write flexible code that works with multiple types

🔧 What are Generics?

Generics let you write code that works with multiple types without duplicating logic. They provide type safety while maintaining flexibility, making your code reusable and efficient.


fn largest(list: &[T]) -> &T {
    // Works with any comparable type
}
                                    
Generic Features

Generic Features

🔧

Generic Functions

Functions that work with any type

fn swap(a: &mut T, b: &mut T) {}
📦

Generic Structs

Data structures for any type

struct Point {
    x: T,
    y: T,
}
🎯

Generic Enums

Enums with type parameters

enum Result {
    Ok(T),
    Err(E),
}
🔗

Trait Bounds

Constrain generic types

fn process(item: T) {}

🔹 Generic Functions

Write functions that work with multiple types:

// Generic function to find the largest item
fn largest(list: &[T]) -> T {
    let mut largest = list[0];
    
    for &item in list {
        if item > largest {
            largest = item;
        }
    }
    
    largest
}

// Generic function to swap two values
fn swap(a: &mut T, b: &mut T) {
    std::mem::swap(a, b);
}

fn main() {
    // Works with integers
    let numbers = vec![34, 50, 25, 100, 65];
    let result = largest(&numbers);
    println!("Largest number: {}", result);
    
    // Works with characters
    let chars = vec!['y', 'm', 'a', 'q'];
    let result = largest(&chars);
    println!("Largest char: {}", result);
    
    // Swap example
    let mut x = 5;
    let mut y = 10;
    println!("Before swap: x = {}, y = {}", x, y);
    swap(&mut x, &mut y);
    println!("After swap: x = {}, y = {}", x, y);
}

Output:

Largest number: 100
Largest char: y
Before swap: x = 5, y = 10
After swap: x = 10, y = 5

🔹 Generic Structs

Create data structures that work with any type:

// Generic struct with one type parameter
struct Point {
    x: T,
    y: T,
}

// Generic struct with multiple type parameters
struct Pair {
    first: T,
    second: U,
}

impl Point {
    fn new(x: T, y: T) -> Self {
        Point { x, y }
    }
}

impl Point {
    fn display(&self) {
        println!("Point({}, {})", self.x, self.y);
    }
}

// Implementation for specific type
impl Point {
    fn distance_from_origin(&self) -> f64 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

fn main() {
    // Integer point
    let int_point = Point::new(5, 10);
    int_point.display();
    
    // Float point
    let float_point = Point::new(1.0, 4.0);
    float_point.display();
    println!("Distance from origin: {:.2}", float_point.distance_from_origin());
    
    // Mixed types
    let pair = Pair {
        first: "Hello",
        second: 42,
    };
    println!("Pair: {} and {}", pair.first, pair.second);
}

Output:

Point(5, 10)
Point(1, 4)
Distance from origin: 4.12
Pair: Hello and 42

🔹 Generic Enums

Enums that can hold different types:

// Custom generic enum
enum Container {
    Empty,
    Single(T),
    Multiple(Vec),
}

impl Container {
    fn new() -> Self {
        Container::Empty
    }
    
    fn add_single(item: T) -> Self {
        Container::Single(item)
    }
    
    fn add_multiple(items: Vec) -> Self {
        Container::Multiple(items)
    }
}

impl Container {
    fn display(&self) {
        match self {
            Container::Empty => println!("Container is empty"),
            Container::Single(item) => println!("Container has one item: {}", item),
            Container::Multiple(items) => {
                println!("Container has {} items:", items.len());
                for item in items {
                    println!("  - {}", item);
                }
            }
        }
    }
}

fn main() {
    let empty: Container = Container::new();
    let single = Container::add_single("Hello");
    let multiple = Container::add_multiple(vec![1, 2, 3, 4, 5]);
    
    empty.display();
    single.display();
    multiple.display();
    
    // Using built-in Option enum
    let some_number = Some(42);
    let no_number: Option = None;
    
    match some_number {
        Some(n) => println!("Got number: {}", n),
        None => println!("No number"),
    }
}

Output:

Container is empty
Container has one item: Hello
Container has 5 items:
- 1
- 2
- 3
- 4
- 5
Got number: 42

🔹 Advanced Generic Constraints

Use complex trait bounds for more specific behavior:

use std::fmt::{Debug, Display};

// Multiple trait bounds
fn print_and_debug(item: &T) 
where 
    T: Display + Debug,
{
    println!("Display: {}", item);
    println!("Debug: {:?}", item);
}

// Generic with lifetime
fn longest<'a, T>(x: &'a T, y: &'a T) -> &'a T 
where 
    T: PartialOrd,
{
    if x > y { x } else { y }
}

// Associated types in traits
trait Iterator {
    type Item;
    fn next(&mut self) -> Option;
}

struct Counter {
    current: usize,
    max: usize,
}

impl Counter {
    fn new(max: usize) -> Counter {
        Counter { current: 0, max }
    }
}

impl Iterator for Counter {
    type Item = usize;
    
    fn next(&mut self) -> Option {
        if self.current < self.max {
            let current = self.current;
            self.current += 1;
            Some(current)
        } else {
            None
        }
    }
}

fn main() {
    let number = 42;
    print_and_debug(&number);
    
    let a = 10;
    let b = 20;
    let larger = longest(&a, &b);
    println!("Larger number: {}", larger);
    
    let mut counter = Counter::new(3);
    while let Some(value) = counter.next() {
        println!("Counter: {}", value);
    }
}

Output:

Display: 42
Debug: 42
Larger number: 20
Counter: 0
Counter: 1
Counter: 2

🧠 Test Your Knowledge

What symbol is used to denote a generic type parameter in Rust?