RustArena

Step 3 of 6

Generic structs and enums

Types can have type parameters too. A point on a game grid uses whole numbers; a GPS position uses decimals. One definition covers both:

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

struct Labeled<T> {
    label: String,
    value: T,
}

#[derive(Debug)]
enum Reading<T> {
    Value(T),
    OutOfRange,
    Missing,
}

fn main() {
    let tile = Point { x: 3, y: 7 };
    let gps = Point { x: 52.52, y: 13.40 };
    println!("tile at ({}, {}), gps at ({}, {})", tile.x, tile.y, gps.x, gps.y);

    let temp = Labeled { label: String::from("kitchen"), value: 21.5 };
    let door = Labeled { label: String::from("front door open"), value: false };
    println!("{}: {}", temp.label, temp.value);
    println!("{}: {}", door.label, door.value);

    let readings: [Reading<u32>; 3] = [Reading::Value(19), Reading::OutOfRange, Reading::Missing];
    for r in &readings {
        match r {
            Reading::Value(v) => println!("{v} degrees"),
            other => println!("no value: {other:?}"),
        }
    }
}
tile at (3, 7), gps at (52.52, 13.4)
kitchen: 21.5
front door open: false
19 degrees
no value: OutOfRange
no value: Missing

Option and Result are generic enums

Reading<T> is your own generic enum, and it follows the same pattern as the two most important enums in Rust:

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

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

Nothing magic: Option<String> and Result<u32, ParseIntError> are these definitions with the parameters filled in.

Common mistakes

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

fn main() {
    let p = Point { x: 5, y: 4.0 };
    println!("{} {}", p.x, p.y);
}
error[E0308]: mismatched types
  |     let p = Point { x: 5, y: 4.0 };
  |                              ^^^ expected integer, found floating-point number

Your task

Fill in the blanks: declare the type parameter of Pair, use it for second, and make total accept only pairs of i32. The program prints:

90 vs 75
ana vs bo
total 165

Challenge 2

A sensor log is a slice of Reading<T> (in the starter). Write fn first_value<T: Copy>(readings: &[Reading<T>]) -> Option<T> that returns the first Value in the slice, skipping Missing entries, or None if there's no value at all.

Challenge 3

From scratch: define a generic struct Labeled<T> with the fields label: String and value: T, then write fn label_all<T>(label: &str, values: Vec<T>) -> Vec<Labeled<T>>, which labels each value <label> #1, <label> #2, and so on. label_all("sensor", vec![21.5, 19.0]) gives two items labeled sensor #1 and sensor #2.

Previous: Trait bounds: what T is allowed to do Next: Methods on generic types: impl<T>