Generics

Back

Loading concept...

🎁 The Magic Box Factory: Understanding Rust Generics

Imagine you have a magical box that can hold ANYTHING—toys, candy, books, even dragons! That’s exactly what Generics are in Rust.


🌟 What Are Generics?

Think of a cookie cutter. One cookie cutter can make cookies from chocolate dough, vanilla dough, or strawberry dough. The shape is the same, but what goes in can change!

Generics let you write code that works with many different types, not just one. Instead of writing the same thing over and over, you write it once and use it everywhere.

// Without generics: Repetitive! 😫
fn print_number(x: i32) { println!("{}", x); }
fn print_text(x: &str) { println!("{}", x); }

// With generics: One function for all! 🎉
fn print_anything<T: std::fmt::Display>(x: T) {
    println!("{}", x);
}

🎯 Generic Functions

A generic function is like a vending machine that accepts any coin—quarters, dimes, or nickels. It doesn’t care which coin you put in; it just does its job!

The <T> Magic Symbol

The letter T is a placeholder. It says: “Hey, I’ll work with whatever type you give me!”

// A function that returns the first item
fn get_first<T>(list: &[T]) -> &T {
    &list[0]
}

fn main() {
    let numbers = [10, 20, 30];
    let words = ["apple", "banana"];

    println!("{}", get_first(&numbers)); // 10
    println!("{}", get_first(&words));   // apple
}

Why This Is Amazing

  • ✅ Write once, use everywhere
  • ✅ No copy-pasting code
  • ✅ Works with ANY type

📦 Generic Structs

Remember our magic box? A generic struct is exactly that—a container that can hold any type!

// A box that holds anything
struct Box<T> {
    item: T,
}

fn main() {
    let toy_box = Box { item: "teddy bear" };
    let number_box = Box { item: 42 };
    let candy_box = Box { item: 3.14 };

    println!("I have: {}", toy_box.item);
}

Real-World Example: A Gift Wrapper

struct Gift<T> {
    content: T,
    wrapped: bool,
}

fn main() {
    let birthday_gift = Gift {
        content: "bicycle",
        wrapped: true,
    };

    let holiday_gift = Gift {
        content: 100, // money!
        wrapped: false,
    };
}

🎭 Generic Enums

Enums can be generic too! They’re like a multi-room house where each room can hold different things.

The Famous Option<T>

This is Rust’s built-in way to say “maybe there’s something here, maybe not.”

enum Option<T> {
    Some(T),  // Yes, we have something!
    None,     // Nope, nothing here
}

fn find_treasure(has_map: bool) -> Option<String> {
    if has_map {
        Some(String::from("Gold coins!"))
    } else {
        None
    }
}

The Famous Result<T, E>

Two generic types! T for success, E for error.

enum Result<T, E> {
    Ok(T),   // Yay! Here's your value
    Err(E),  // Oops! Here's what went wrong
}

fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err(String::from("Can't divide by zero!"))
    } else {
        Ok(a / b)
    }
}

🔧 Generic Methods

Methods on generic structs can use the same generic type. It’s like giving your magic box special powers!

struct Wrapper<T> {
    value: T,
}

impl<T> Wrapper<T> {
    // Create a new wrapper
    fn new(value: T) -> Self {
        Wrapper { value }
    }

    // Peek inside
    fn peek(&self) -> &T {
        &self.value
    }

    // Take the value out
    fn unwrap(self) -> T {
        self.value
    }
}

fn main() {
    let gift = Wrapper::new("surprise!");
    println!("Inside: {}", gift.peek());
}

Methods with Extra Generic Types

Sometimes a method needs its OWN generic type!

impl<T> Wrapper<T> {
    // Transform into a different type
    fn transform<U>(self, new_value: U) -> Wrapper<U> {
        Wrapper { value: new_value }
    }
}

🎪 Multiple Generic Types

What if your box needs TWO different types? No problem!

// A pair that holds two different things
struct Pair<T, U> {
    first: T,
    second: U,
}

fn main() {
    let combo = Pair {
        first: "hello",      // &str
        second: 42,          // i32
    };

    let another = Pair {
        first: 3.14,         // f64
        second: true,        // bool
    };
}

A Coordinate Example

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

fn main() {
    // Integer coordinates
    let pixel = Point { x: 10, y: 20 };

    // Mixed coordinates
    let mixed = Point { x: 5, y: 4.5 };

    // Both floating point
    let precise = Point { x: 1.5, y: 2.7 };
}

⚡ Monomorphization

Here’s a secret superpower of Rust generics: they have ZERO runtime cost!

How? Monomorphization!

When you compile your code, Rust creates separate versions for each type you use. It’s like a photocopier that makes custom copies!

// You write this ONCE:
fn double<T: std::ops::Add<Output = T> + Copy>(x: T) -> T {
    x + x
}

fn main() {
    double(5);     // i32
    double(3.14);  // f64
}
// Rust compiles it as if you wrote:
fn double_i32(x: i32) -> i32 { x + x }
fn double_f64(x: f64) -> f64 { x + x }

Why This Matters

Feature Speed Binary Size
Generics 🚀 Fast as hand-written Larger (copies)
Dynamic Slower (runtime lookup) Smaller

You get: Speed of specific code + convenience of generic code!


📏 Const Generics

What if you want a generic… number? Like an array of exactly 5 items, or 10 items, or any amount?

Const generics let you use values (not just types) as generic parameters!

// An array of ANY size
struct FixedArray<T, const N: usize> {
    data: [T; N],
}

fn main() {
    let small: FixedArray<i32, 3> = FixedArray {
        data: [1, 2, 3],
    };

    let big: FixedArray<i32, 100> = FixedArray {
        data: [0; 100],
    };
}

Practical Example: Safe Buffers

fn create_buffer<const SIZE: usize>() -> [u8; SIZE] {
    [0; SIZE]
}

fn main() {
    let tiny = create_buffer::<4>();    // [0, 0, 0, 0]
    let medium = create_buffer::<64>(); // 64 zeros
}

Matrix Math with Const Generics

struct Matrix<T, const ROWS: usize, const COLS: usize> {
    data: [[T; COLS]; ROWS],
}

fn main() {
    let grid: Matrix<i32, 3, 3> = Matrix {
        data: [
            [1, 2, 3],
            [4, 5, 6],
            [7, 8, 9],
        ],
    };
}

🎓 Quick Summary

graph TD A["🎁 Generics"] --> B["Generic Functions"] A --> C["Generic Structs"] A --> D["Generic Enums"] A --> E["Generic Methods"] A --> F["Multiple Types"] A --> G["Monomorphization"] A --> H["Const Generics"] B --> B1["fn do_thing&lt;T&gt;&#35;40;x: T&#35;41;"] C --> C1["struct Box&lt;T&gt; &#123; item: T &#125;"] D --> D1["enum Option&lt;T&gt;"] E --> E1["impl&lt;T&gt; Box&lt;T&gt;"] F --> F1["struct Pair&lt;T, U&gt;"] G --> G1["Zero-cost at runtime!"] H --> H1["struct Array&lt;T, const N: usize&gt;"]

🌈 The Big Picture

Concept What It Does Example
Generic Function Works with any type fn print<T>(x: T)
Generic Struct Holds any type struct Box<T>
Generic Enum Variants with any type Option<T>
Generic Method Methods on generic types impl<T> Box<T>
Multiple Generics Use several types Pair<T, U>
Monomorphization Compile-time optimization Fast + type-safe
Const Generics Generic over values [T; N]

🎉 You Did It!

Generics are like learning to use a universal remote—once you get it, you can control everything with one tool!

Remember:

  • <T> means “any type goes here”
  • Rust makes generics FAST through monomorphization
  • Const generics let you be generic over numbers too

Now go build your own magic boxes! 🎁✨

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.