Generics in Rust

Introduction

Welcome to the lesson on Generics in Rust. As you advance your Rust programming skills, mastering generics will be essential for writing flexible, efficient, and type-safe code. Generics allow you to write definitions for functions, structs, enums, and methods that can work with any data type, enabling code reuse and reducing duplication.

In earlier lessons, you've explored structs, implementations, enums, and pattern matching. This lesson builds on that foundation, diving deeper into creating more dynamic and flexible code using generics.

Understanding Generics in Rust

Generics enable you to write code that can handle values identically without knowing their exact types in advance. Let's start by examining a function that finds the largest item in a slice of elements.

Here's a basic function that finds the largest integer in a slice:

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

In this function, we iterate over a slice of i32 integers to find the largest number. However, this function only works for slices of integers. To make it more versatile, we can introduce generics so that it works with any data type that can be compared:

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

fn main() {
    let numbers = vec![1, 5, 2, 8, 3];
    let chars = vec!['a', 'z', 'b', 'y'];

    println!("Largest number: {}", largest(&numbers)); // 8
    println!("Largest char: {}", largest(&chars));     // 'z'
}

In this version, T is a generic type parameter. We specify that T must implement the PartialOrd and Copy traits. PartialOrd allows comparison between elements, and Copy ensures that values can be copied rather than moved. With these constraints, the largest function now works with any slice of comparable and copyable types.

Using Generics with Structs

Generics are not limited to functions; you can also define structs with generic parameters, making them more flexible to use with different data types. Let's see an example:

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

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

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

In this code, Point defines a generic type T for its fields x and y, allowing us to create Point instances with any data type. This reduces code duplication and allows for more abstract and reusable data structures.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal