🎓 A1Learner
🔍

Rust Generics Basics

Generics let one function work for many types. A generic function names a placeholder type in angle brackets instead of one fixed type.

The same job, written twice

Handing back the first item is one job. Written for two types, the bodies come out identical — only the type names differ:

fn first_i32(list: &[i32]) -> i32 {
    list[0]
}

fn first_char(list: &[char]) -> char {
    list[0]
}

fn main() {
    println!("first number = {}", first_i32(&[3, 9, 4]));
    println!("first letter = {}", first_char(&['a', 'z', 'm']));
}

One function with a placeholder

The generic version names the placeholder T in angle brackets. The body never mentions a real type:

fn first<T>(list: &[T]) -> &T {
    &list[0]
}

fn main() {
    let numbers = [3, 9, 4];
    let letters = ['a', 'z', 'm'];
    println!("first number = {}", first(&numbers));
    println!("first letter = {}", first(&letters));
}

A struct can hold a placeholder

A struct names the placeholder the same way. Both fields share the one T, so the two values in a Pair always match in type:

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

fn main() {
    let numbers = Pair { first: 3, second: 9 };
    println!("pair = {} and {}", numbers.first, numbers.second);
    let words = Pair { first: "up", second: "down" };
    println!("pair = {} and {}", words.first, words.second);
}

Names from earlier lessons are generic

Vec, Option and Result are generic types too. Option holds a value that may be missing. Result holds a value, or an error instead. The brackets name the type each one works with:

fn main() {
    let scores: Vec<i32> = vec![10, 20, 30];
    let second: Option<&i32> = scores.get(1);
    let total: Result<i32, String> = Ok(scores[0] + scores[1]);
    println!("scores = {:?}", scores);
    println!("second = {:?}", second);
    println!("total = {:?}", total);
}

The turbofish names the type

parse can build many types from the same text, so Rust cannot guess here. The ::<i32> spelling, called the turbofish, names the type at the call:

fn main() {
    let n = "42".parse::<i32>();
    println!("as a whole number = {:?}", n);
    let m = "42".parse::<f64>();
    println!("as a decimal = {:?}", m);
}

Example

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

fn first<T>(list: &[T]) -> &T {
    &list[0]
}

fn main() {
    let numbers = Pair { first: 3, second: 9 };
    println!("pair = {} and {}", numbers.first, numbers.second);
    let words = Pair { first: "left", second: "right" };
    println!("pair = {} and {}", words.first, words.second);
    let scores = [10, 20, 30];
    println!("first score = {}", first(&scores));
    let letters = ['x', 'y'];
    println!("first letter = {}", first(&letters));
}

Key points

  • One generic definition replaces a copy of the code for every type.
  • T is the placeholder name used most often.
  • Rust fills the placeholder with the real type at each call.
  • Vec<T>, Option<T> and Result<T, E> are generic types from the standard library.
  • Use the turbofish ::<...> to name a type that Rust cannot guess.

In this example, Pair is a generic struct. Its two fields share the placeholder T. The first Pair fills T with i32, and the second fills it with &str. The function first is generic too. It hands back the first item of any list it is given. main calls it on a list of numbers and on a list of letters. Rust Generics, in the Intermediate course, goes deeper from here — it adds promises about T, called bounds, so a generic function can compare and print the values it holds.

Note

A placeholder can be any name, like E for an error type. Single letters are the habit, and T is the most common.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

What does the T in fn first<T>(list: &[T]) -> &T stand for?