SyntaxStudy
Sign Up
Rust Generic Functions and Structs
Rust Beginner 1 min read

Generic Functions and Structs

Generics allow you to write a single piece of code that works for many different types. A generic function declares type parameters in angle brackets after the function name: `fn largest(list: &[T]) -> &T`. The compiler performs monomorphisation — it analyses all call sites and generates a concrete version of the function for each distinct type used, so the resulting binary contains no generic code and carries no runtime overhead. Generic structs work the same way: `struct Pair { first: T, second: T }` can hold any type in both fields. You can implement methods on a generic struct either for all values of the type parameter (`impl Pair`) or for a specific type by providing a concrete type (`impl Pair`). Adding trait bounds to the `impl` block constrains which types can use that block of methods. Rust's generics are fundamentally different from Java's type erasure generics — there is no boxing, no dynamic dispatch, and no `Object` casting needed. The type information is fully preserved at compile time, allowing the compiler to optimise generic code as aggressively as manually specialised code. This is what Rust means by "zero-cost abstractions".
Example
// Generic function — T must implement PartialOrd for comparison
fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut max = &list[0];
    for item in list {
        if item > max {
            max = item;
        }
    }
    max
}

// Generic struct
#[derive(Debug)]
struct Pair<T> {
    first: T,
    second: T,
}

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

// Methods available only when T implements Display + PartialOrd
impl<T: std::fmt::Display + PartialOrd> Pair<T> {
    fn larger(&self) -> &T {
        if self.first >= self.second { &self.first } else { &self.second }
    }
}

// Generic enum — like Option in the standard library
#[derive(Debug)]
enum Maybe<T> {
    Just(T),
    Nothing,
}

impl<T: std::fmt::Display> Maybe<T> {
    fn show(&self) {
        match self {
            Maybe::Just(v) => println!("Just({v})"),
            Maybe::Nothing => println!("Nothing"),
        }
    }
}

fn main() {
    let ints = vec![34, 50, 25, 100, 65];
    println!("largest int = {}", largest(&ints));

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

    let pair = Pair::new(5, 10);
    println!("larger = {}", pair.larger());

    let maybe_str: Maybe<&str> = Maybe::Just("hello");
    maybe_str.show();
    let nothing: Maybe<i32> = Maybe::Nothing;
    nothing.show();
}