Why Generics?
Ever felt like writing the same function or struct multiple times for different data types? Generics solve that! They let you write reusable, flexible, and type-safe code. Instead of hardcoding types, generics let you use placeholders. Think of them as templates, but cooler!
1. Generics in Functions
A function can be made generic by using type parameters. Instead of specifying a concrete type (like i32
), we use a placeholder (T
).
Example: Generic Function
fn print_value<T: std::fmt::Debug>(value: T) {
println!("{:?}", value);
}
fn main() {
print_value(42);
print_value("Hello, Rust!");
}
T
is a placeholder for any type. The std::fmt::Debug
trait ensures T
can be printed. Works with multiple types without rewriting the function!
2. Generics in Structs
Want a struct that can hold any type? Generics let you define flexible data structures.
struct Boxed<T> {
value: T,
}
fn main() {
let int_box = Boxed { value: 10 };
let str_box = Boxed { value: "Rust" };
println!("{}", int_box.value);
println!("{}", str_box.value);
}
T
makes Boxed
work with any type. No need to create separate structs for i32
, String
, etc. More reusable and maintainable code!
3. Generics in Enums
Rust’s standard library uses generics in enums like Option<T>
and Result<T, E>
.
Example: Custom Enum with Generics
enum Pair<T, U> {
First(T),
Second(U),
}
fn main() {
let num = Pair::First(42);
let text = Pair::Second("Hello");
}
Pair<T, U>
allows storing different types. Works similarly to built-in enums like Option<T>
.
4. Generics with Traits (Trait Bounds)
Sometimes, we want our generic type to support specific behaviors. That’s where trait bounds come in.
fn double<T: std::ops::Mul<Output = T> + Copy>(x: T) -> T {
x * x
}
fn main() {
println!("{}", double(5));
println!("{}", double(3.5));
}
T: std::ops::Mul<Output = T>
ensures T
supports multiplication. The Copy
trait ensures T
can be copied safely. Now we can double numbers, whether integers or floats!
5. Implementing Traits for Generic Types
We can also define generic implementations for structs with generics!
struct Container<T> {
item: T,
}
impl<T: std::fmt::Display> Container<T> {
fn show(&self) {
println!("Item: {}", self.item);
}
}
fn main() {
let c = Container { item: 99 };
c.show();
}
T: std::fmt::Display
ensures T
can be printed. Container<T>
is now flexible and can store any printable type!
6. Performance? Zero-Cost Abstraction!
Unlike some languages where generics introduce performance overhead, Rust’s generics are monomorphized at compile time. This means: No runtime cost! Code is optimized per type at compile time. Same efficiency as manually written functions for each type!
Conclusion: Generics = Power + Flexibility
Now you know: Generics make Rust code reusable for different types. Functions, structs, and enums can be generic. Trait bounds ensure only valid types are used. Performance is not compromised!
Time to embrace generics and write cleaner, reusable Rust code!
0 Comments