BudiBadu Logo
Samplebadu

Rust by Example: Generics Basics

Rust 1.75+

Write flexible and reusable code with Generics. This guide explains how to define functions and structs that work with any type, and how to use trait bounds to restrict generics to types with specific behavior.

Code

// A generic function
// T can be any type that implements PartialOrd (for comparison)
fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];

    for item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

// A generic struct
struct Point<T> {
    x: T,
    y: T,
}

// Methods for generic struct
impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

// Methods for specific type only
impl Point<f32> {
    fn distance_from_origin(&self) -> f32 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

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

    let char_list = vec!['y', 'm', 'a', 'q'];
    let result = largest(&char_list);
    println!("The largest char is {}", result);
    
    let integer_point = Point { x: 5, y: 10 };
    let float_point = Point { x: 1.0, y: 4.0 };
    
    println!("Float distance: {}", float_point.distance_from_origin());
    // integer_point.distance_from_origin(); // Error: not defined for Point<i32>
}

Explanation

Generics allow you to write code that works with multiple types, reducing duplication. Rust implements generics using a technique called Monomorphization. At compile time, the compiler looks at all the places where generic code is called and generates specific, optimized machine code for each concrete type used.

  • Zero Runtime Cost: Because specific versions are generated, there is no runtime overhead (like boxing or vtable lookups) associated with using generics. It is as fast as hand-written code for each type.
  • Code Bloat: The trade-off is that the binary size can increase if a generic function is instantiated with many different types.

To ensure type safety, Rust uses Trait Bounds (e.g., T: PartialOrd). This tells the compiler that the generic type T must implement specific behavior (like comparison), allowing those methods to be used within the generic function.

Code Breakdown

3
fn largest. This is the generic signature. It declares a type parameter T and restricts it to types that implement the PartialOrd trait, which enables the > operator.
16
struct Point. This struct can hold coordinates of any type, but x and y must be the same type. If you wanted mixed types, you would define struct Point.
29
impl Point. This block adds methods only to Points where T is f32. This allows us to use math operations like sqrt() which are only defined for floats, without breaking the struct for other types.