Labs ICT
โญ Pro Login

Generics

Writing code that works with many types.

Generics in Rust

Generics let you write code that works with many different types. They're like templates that the compiler fills in with concrete types at compile time.

You define generics with angle brackets <T>. The T is a type parameter that can be any type.

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

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

    let chars = vec!['y', 'm', 'a', 'q'];
    let result = largest(&chars);
    println!("Largest char: {}", result);
}
Try it Yourself ->

Generic Structs

Structs can be generic too. This lets you create flexible data structures that work with any type.

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

struct MixedPoint<T, U> {
    x: T,
    y: U,
}

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

    println!("Integer point: ({}, {})", integer_point.x, integer_point.y);
    println!("Float point: ({}, {})", float_point.x, float_point.y);

    let mixed = MixedPoint { x: 5, y: 4.0 };
    println!("Mixed point: ({}, {})", mixed.x, mixed.y);
}
Try it Yourself ->

Generic Enums

Enums can be generic too. The standard library's Option and Result types are perfect examples of generic enums.

enum Result<T, E> {
    Ok(T),
    Err(E),
}

enum Option<T> {
    Some(T),
    None,
}

fn main() {
    let some_number: Option<i32> = Some(5);
    let some_string: Option<String> = Some("hello".to_string());
    let no_number: Option<i32> = None;

    println!("Some number: {:?}", some_number);
    println!("Some string: {:?}", some_string);
    println!("No number: {:?}", no_number);
}
Try it Yourself ->

Generic impl Blocks

You can implement methods for generic types. This lets you add behavior to your generic structs.

struct Pair<T> {
    first: T,
    second: T,
}

impl<T> Pair<T> {
    fn new(first: T, second: T) -> Self {
        Pair { first, second }
    }

    fn first(&self) -> &T {
        &self.first
    }

    fn second(&self) -> &T {
        &self.second
    }
}

fn main() {
    let pair = Pair::new(1, 2);
    println!("First: {}, Second: {}", pair.first(), pair.second());

    let string_pair = Pair::new("hello".to_string(), "world".to_string());
    println!("First: {}, Second: {}", string_pair.first(), string_pair.second());
}
Try it Yourself ->

Monomorphization: Zero-Cost Generics

Rust uses monomorphization to make generics fast. The compiler creates specialized versions of your generic code for each concrete type used. This means generics have no runtime cost.

You get the flexibility of generics with the performance of hand-written, type-specific code. It's the best of both worlds.

Generics vs Trait Objects

Generics give you static dispatch (compile-time), while trait objects give you dynamic dispatch (runtime). Generics are usually faster but can lead to code bloat. Trait objects are more flexible but have a small performance cost.

Use generics when you know all the types at compile time. Use trait objects when you need to work with different types at runtime.

๐Ÿงช Quick Quiz

What keyword defines a generic function?