Generic Types
Using generic types for code reusability.
Generic Types
Generic types allow us to write code that works with multiple different types.
Generic Functions
RUSTfn largest<T>(list: &[T]) -> &T { let mut largest = &list[0]; for item in list { if item > largest { // This won't work yet - trait needed! largest = item; } } largest }
Generic Structs
RUSTstruct Point<T> { x: T, y: T, } let integer_point = Point { x: 5, y: 10 }; let float_point = Point { x: 1.0, y: 4.0 };
Multiple Generic Parameters
RUSTstruct Point<T, U> { x: T, y: U, } let point = Point { x: 5, y: 4.0 };
Generic Enums
RUSTenum Option<T> { Some(T), None, } enum Result<T, E> { Ok(T), Err(E), }
Generic Methods
RUSTimpl<T> Point<T> { fn x(&self) -> &T { &self.x } } impl Point<f32> { fn distance_from_origin(&self) -> f32 { (self.x.powi(2) + self.y.powi(2)).sqrt() } }
Performance
Generic types have no runtime overhead! Rust uses monomorphization - generates separate code for each type at compile time.
Code Examples
Generic Function
Generic function definition
fn largest<T: PartialOrd>(list: &[T]) -> Option<&T> {
if list.is_empty() {
return None;
}
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
Some(largest)
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number: {:?}", result);
}Explanation:
The <T> syntax indicates the function is generic. The T: PartialOrd trait bound is needed for comparison. The function works with any comparable type.
Generic Struct
Generic struct definition
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn new(x: T, y: T) -> Point<T> {
Point { x, y }
}
fn x(&self) -> &T {
&self.x
}
}
fn main() {
let integer_point = Point::new(5, 10);
let float_point = Point::new(1.0, 4.0);
println!("Integer point x: {}", integer_point.x());
println!("Float point x: {}", float_point.x());
}Explanation:
Generic structs allow us to use the same structure with different types. The T type parameter is replaced with the concrete type.
Multiple Generic Parameters
Struct with multiple generic types
struct Point<T, U> {
x: T,
y: U,
}
impl<T, U> Point<T, U> {
fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
Point {
x: self.x,
y: other.y,
}
}
}
fn main() {
let p1 = Point { x: 5, y: 10.4 };
let p2 = Point { x: "Hello", y: 'c' };
let p3 = p1.mixup(p2);
println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}Explanation:
We can use multiple generic parameters. This allows us to combine different types in the same struct.
Blockchain: Generic Storage
Using generics for blockchain data storage
struct Storage<T> {
data: Vec<T>,
}
impl<T> Storage<T> {
fn new() -> Self {
Storage { data: Vec::new() }
}
fn add(&mut self, item: T) {
self.data.push(item);
}
fn get(&self, index: usize) -> Option<&T> {
self.data.get(index)
}
fn len(&self) -> usize {
self.data.len()
}
}
struct Block {
hash: String,
}
struct Transaction {
id: String,
}
fn main() {
let mut block_storage: Storage<Block> = Storage::new();
block_storage.add(Block { hash: String::from("hash1") });
let mut tx_storage: Storage<Transaction> = Storage::new();
tx_storage.add(Transaction { id: String::from("tx1") });
println!("Blocks: {}, Transactions: {}",
block_storage.len(), tx_storage.len());
}Explanation:
Generics allow us to create reusable storage structures for different blockchain data types (blocks, transactions, etc.) without code duplication.
Exercises
Generic Struct
Create a generic Pair struct!
Starter Code:
fn main() {
let mut pair = Pair::new(1, 2);
pair.swap();
println!("Pair: ({}, {})", pair.first, pair.second);
}