🎁 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<T>#40;x: T#41;"] C --> C1["struct Box<T> { item: T }"] D --> D1["enum Option<T>"] E --> E1["impl<T> Box<T>"] F --> F1["struct Pair<T, U>"] G --> G1["Zero-cost at runtime!"] H --> H1["struct Array<T, const N: usize>"]
🌈 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! 🎁✨
