Generic Types

Using generic types for code reusability.

Intermediate⏱️ 40 min📚 Prerequisites: 1

Generic Types

Generic types allow us to write code that works with multiple different types.

Generic Functions

RUST
fn 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

RUST
struct 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

RUST
struct Point<T, U> {
    x: T,
    y: U,
}

let point = Point { x: 5, y: 4.0 };

Generic Enums

RUST
enum Option<T> {
    Some(T),
    None,
}

enum Result<T, E> {
    Ok(T),
    Err(E),
}

Generic Methods

RUST
impl<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

RUST
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

RUST
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

RUST
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

RUST
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!

Medium

Starter Code:

RUST
fn main() {
    let mut pair = Pair::new(1, 2);
    pair.swap();
    println!("Pair: ({}, {})", pair.first, pair.second);
}