Generics in Rust: Why They're Needed and How to Use Them for Beginners

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Generics in Rust: Why They're Needed and How to Use Them

Generics are one of the key concepts in Rust, allowing you to write flexible, reusable, and safe code. If you're just starting to learn Rust, understanding generics will open the door to writing efficient libraries and applications. In this article, we'll break down what Generics are, why they're needed, and how to apply them in practice.



What are Generics and Why Are They Needed?

Imagine you need to write a function that finds the maximum element in an array. For numbers, it's one implementation; for strings, it's another. To avoid copying code for each type, Rust offers generic programming using Generics.

Generics allow you to define structures, functions, enums, or methods that work with an arbitrary data type without being tied to a specific one. The compiler substitutes the real type at compile time — this process is called monomorphization.

Without generics, you would write:

fn max_i32(list: &[i32]) -> i32 {    let mut max = list[0];    for &item in list {        if item > max {            max = item;        }    }    max}

fn max_f64(list: &[f64]) -> f64 { let mut max = list[0]; for &item in list { if item > max { max = item; } } max}

With generics, this turns into one universal function.



Basics of Using Generics

Generic Functions

To make a function generic, you need to specify a type parameter in angle brackets <T>:

fn max<T>(list: &[T]) -> T {    let mut max = &list[0];    for item in list {        if item > max {            max = item;        }    }    *max}

But this code won't compile — the compiler will say that the PartialOrd trait is not implemented for type T. This is where trait bounds come into play.



Generic Structs and Enums

Structs can also be generic:

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

fn main() { let int_point = Point { x: 5, y: 10 }; let float_point = Point { x: 1.5, y: 3.8 };}

You can specify multiple type parameters:

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

Enums also support generics — think of Option<T> or Result<T, E> from the standard library.



Generic Methods

You can define methods for structs with generics:

impl<T> Point<T> {    fn x(&self) -> &T {        &self.x    }}

Or only for a specific type:

impl Point<f64> {    fn distance_from_origin(&self) -> f64 {        (self.x.powi(2) + self.y.powi(2)).sqrt()    }}


Trait Bounds

To use comparison, addition, or other operations, you need to specify which traits the type implements. Let's go back to the max function:

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

Now the code compiles — we've stated that T must support partial ordering (PartialOrd).

You can combine traits with +:

fn clone_and_compare<T: Clone + PartialOrd>(a: T, b: T) -> bool {    a.clone() > b.clone()}

Or use the where syntax for readability:

fn some_function<T, U>(t: &T, u: &U) -> i32where    T:                

Blogs

Book Recommendations