Rust Generics
Write flexible code that works with multiple types
🔧 What are Generics?
Generics let you write code that works with multiple types without duplicating logic. They provide type safety while maintaining flexibility, making your code reusable and efficient.
fn largest(list: &[T]) -> &T {
// Works with any comparable type
}
Generic Features
Generic Functions
Functions that work with any type
fn swap(a: &mut T, b: &mut T) {}
Generic Structs
Data structures for any type
struct Point {
x: T,
y: T,
}
Generic Enums
Enums with type parameters
enum Result {
Ok(T),
Err(E),
}
Trait Bounds
Constrain generic types
fn process(item: T) {}
🔹 Generic Functions
Write functions that work with multiple types:
// Generic function to find the largest item
fn largest(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
// Generic function to swap two values
fn swap(a: &mut T, b: &mut T) {
std::mem::swap(a, b);
}
fn main() {
// Works with integers
let numbers = vec![34, 50, 25, 100, 65];
let result = largest(&numbers);
println!("Largest number: {}", result);
// Works with characters
let chars = vec!['y', 'm', 'a', 'q'];
let result = largest(&chars);
println!("Largest char: {}", result);
// Swap example
let mut x = 5;
let mut y = 10;
println!("Before swap: x = {}, y = {}", x, y);
swap(&mut x, &mut y);
println!("After swap: x = {}, y = {}", x, y);
}
Output:
Largest char: y
Before swap: x = 5, y = 10
After swap: x = 10, y = 5
🔹 Generic Structs
Create data structures that work with any type:
// Generic struct with one type parameter
struct Point {
x: T,
y: T,
}
// Generic struct with multiple type parameters
struct Pair {
first: T,
second: U,
}
impl Point {
fn new(x: T, y: T) -> Self {
Point { x, y }
}
}
impl Point {
fn display(&self) {
println!("Point({}, {})", self.x, self.y);
}
}
// Implementation for specific type
impl Point {
fn distance_from_origin(&self) -> f64 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
fn main() {
// Integer point
let int_point = Point::new(5, 10);
int_point.display();
// Float point
let float_point = Point::new(1.0, 4.0);
float_point.display();
println!("Distance from origin: {:.2}", float_point.distance_from_origin());
// Mixed types
let pair = Pair {
first: "Hello",
second: 42,
};
println!("Pair: {} and {}", pair.first, pair.second);
}
Output:
Point(1, 4)
Distance from origin: 4.12
Pair: Hello and 42
🔹 Generic Enums
Enums that can hold different types:
// Custom generic enum
enum Container {
Empty,
Single(T),
Multiple(Vec),
}
impl Container {
fn new() -> Self {
Container::Empty
}
fn add_single(item: T) -> Self {
Container::Single(item)
}
fn add_multiple(items: Vec) -> Self {
Container::Multiple(items)
}
}
impl Container {
fn display(&self) {
match self {
Container::Empty => println!("Container is empty"),
Container::Single(item) => println!("Container has one item: {}", item),
Container::Multiple(items) => {
println!("Container has {} items:", items.len());
for item in items {
println!(" - {}", item);
}
}
}
}
}
fn main() {
let empty: Container = Container::new();
let single = Container::add_single("Hello");
let multiple = Container::add_multiple(vec![1, 2, 3, 4, 5]);
empty.display();
single.display();
multiple.display();
// Using built-in Option enum
let some_number = Some(42);
let no_number: Option = None;
match some_number {
Some(n) => println!("Got number: {}", n),
None => println!("No number"),
}
}
Output:
Container has one item: Hello
Container has 5 items:
- 1
- 2
- 3
- 4
- 5
Got number: 42
🔹 Advanced Generic Constraints
Use complex trait bounds for more specific behavior:
use std::fmt::{Debug, Display};
// Multiple trait bounds
fn print_and_debug(item: &T)
where
T: Display + Debug,
{
println!("Display: {}", item);
println!("Debug: {:?}", item);
}
// Generic with lifetime
fn longest<'a, T>(x: &'a T, y: &'a T) -> &'a T
where
T: PartialOrd,
{
if x > y { x } else { y }
}
// Associated types in traits
trait Iterator {
type Item;
fn next(&mut self) -> Option;
}
struct Counter {
current: usize,
max: usize,
}
impl Counter {
fn new(max: usize) -> Counter {
Counter { current: 0, max }
}
}
impl Iterator for Counter {
type Item = usize;
fn next(&mut self) -> Option {
if self.current < self.max {
let current = self.current;
self.current += 1;
Some(current)
} else {
None
}
}
}
fn main() {
let number = 42;
print_and_debug(&number);
let a = 10;
let b = 20;
let larger = longest(&a, &b);
println!("Larger number: {}", larger);
let mut counter = Counter::new(3);
while let Some(value) = counter.next() {
println!("Counter: {}", value);
}
}
Output:
Debug: 42
Larger number: 20
Counter: 0
Counter: 1
Counter: 2