Welcome to Day 17 of my #100DaysOfRust journey! Today was all about stepping into Generics, a powerful feature in Rust that helps us write flexible, reusable, and clean code. We also began peeking into Traits and Lifetimes, both of which are key pillars of Rust's type system.
Let’s break it all down.
📅 Removing Duplication with Functions
We began by solving a simple problem: finding the largest number in a list. Here’s a basic implementation:
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let mut largest = &number_list[0];
for number in &number_list {
if number > largest {
largest = number;
}
}
println!("The largest number is {largest}");
}
Duplicating this code for multiple lists becomes tedious. So we extracted a function:
fn largest(list: &[i32]) -> &i32 {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
Clean and reusable!
🧰 Introducing Generics in Functions
But what if we wanted the same function to work on characters?
fn largest_char(list: &[char]) -> &char {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
Still duplication! So we introduce Generics:
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
-
T
is a generic type. -
PartialOrd
ensures we can compare values. -
Copy
ensures we can duplicate values safely.
🔹 Using Generics in Structs
struct Point<T> {
x: T,
y: T,
}
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
Or mix types:
struct Point<T, U> {
x: T,
y: U,
}
let mixed = Point { x: 5, y: 4.0 };
🔠 Generics in Enums
Rust’s built-in enums like Option<T>
and Result<T, E>
use generics:
enum Option<T> {
Some(T),
None,
}
enum Result<T, E> {
Ok(T),
Err(E),
}
We can define our own generic enums in a similar way.
💼 Generics in Methods
We can also define methods on generic types:
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
You can even specialize for specific types:
impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
🌐 Combining Generic Parameters
struct Point<X1, Y1> {
x: X1,
y: Y1,
}
impl<X1, Y1> Point<X1, Y1> {
fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
Point {
x: self.x,
y: other.y,
}
}
}
Mix and match!
🧹 Monomorphization: Zero Runtime Cost
Rust uses a process called monomorphization to replace generic types with concrete types at compile time. This means:
- No runtime performance cost.
- You get the efficiency of handwritten type-specific code.
Example:
let integer = Some(5);
let float = Some(5.0);
Compiles to:
enum Option_i32 { Some(i32), None }
enum Option_f64 { Some(f64), None }
🔹 Summary
Today we covered:
- Removing duplication with extracted functions
- Introducing generics in functions, structs, enums, and methods
- Trait bounds like
PartialOrd
,Copy
- Specializing method implementations
- Monomorphization for performance
Up next: Traits and Lifetimes!
Stay tuned as we go deeper into Rust's type system.
Top comments (0)