Activity 22 - Generics

Overview

In this activity, you'll practice writing generic functions and structs, working with trait bounds, and understanding how Rust's type system enables flexible, reusable code. All exercises can be completed in the Rust Playground.

Part 1: Fix the Trait Bounds (Warm-up)

Learning goal: Understanding which trait bounds are needed for different operations

Important Note: Some traits need to be imported! Copy this template to start each problem in the Rust Playground

#![allow(unused)]
fn main() {
// Common trait imports you might need:
#![allow(unused_imports)] // to ignore unused import warnings
use std::fmt::{Debug, Display};
use std::cmp::{PartialOrd, PartialEq, Eq, Ord};
use std::ops::{Add, Sub, Mul, Div};
// Note: Copy, Clone are automatically imported
}

Instructions For each problem copy the snippet into Rust Playground and work on it until it compiles. The compiler errors will be very helpful!

Problem 1.1: Printing with Debug

Fix this function by adding the correct trait bound(s):

// Fix this function so it compiles
fn print_twice<T>(value: T) {
    println!("{:?}", value);
}

fn main() {
    print_twice(42);
    print_twice("hello");
}

Problem 1.2: Comparison

Fix this function by adding the correct trait bound:

// Fix this function so it compiles
fn is_greater<T>(a: T, b: T) -> bool {
    a > b
}

fn main() {
    println!("{}", is_greater(5, 3));
    println!("{}", is_greater(2.5, 7.8));
}

Problem 1.3: Multiple Uses with Display

Fix this function by adding the correct trait bounds (you'll need multiple!):

// Fix this function so it compiles
fn compare_and_print<T>(a: T, b: T) {
    if a > b {
        println!("{:?} is greater", a);
    } else if a == b {
        println!("They are equal!");
    } else {
        println!("{:?} is greater", b);
    }
}

fn main() {
    compare_and_print(5, 3);
    compare_and_print(2.5, 7.8);
}

Part 2: Build a Generic Container

Learning goal: Creating and implementing methods for generic structs

Implement a generic Pair struct that holds two values of the same type.

#[derive(Debug)]
struct Pair<T> {
    first: T
    second: T
}

// 1. Implement a constructor method `new`
impl<T> Pair<T> {
    // fn new(first: T, second: T) -> Pair<T>
}

// 2. Implement a method `swap` that returns a new Pair with values swapped
// You can do this without adding a trait bound!
impl<T> Pair<T> {
    // fn swap(self) -> Pair<T>
}

// 3. Implement a method `are_equal` that returns true if first == second
//    (You'll need a trait bound on this impl block!)
impl<T: ???> Pair<T> {
    // fn are_equal(&self) -> bool
}

// Work until this compiles!
fn main() {
    let pair = Pair::new(5, 10);
    println!("Original: {:?}", pair);

    let swapped = pair.swap();
    println!("Swapped: {:?}", swapped);

    println!("Are equal? {}", pair.are_equal());

    let equal_pair = Pair::new(7, 7);
    println!("Are equal? {}", equal_pair.are_equal());
}

Bonus: Add a method max that returns a reference to the larger of the two values. What trait bound do you need?

Part 3: Two Different Types

Learning goal: Working with multiple type parameters

Sometimes you want to store two values of DIFFERENT types. Implement this:

#[derive(Debug)]
struct Pair<T, U> {
    first: T
    second: U
}

// Implement methods:
impl<T, U> Pair<T, U> {
    // 1. Constructor
    fn new(first: T, second: U) -> Pair<T, U> {
        // Your code here
    }

    // 2. Get first value as reference
    fn get_first(&self) -> &T {
        // Your code here
    }

    // 3. Get second value as reference
    fn get_second(&self) -> &U {
        // Your code here
    }

    // 4. Swap the values (notice the return type!)
    fn swap(self) -> Pair<U, T> {
        // Your code here
    }
}

fn main() {
    let mixed = Pair::new(42, "hello");
    println!("First: {}, Second: {}", mixed.get_first(), mixed.get_second());

    let swapped = mixed.swap();
    println!("Swapped: {:?}", swapped);
    // Now swapped is Pair<&str, i32> instead of Pair<i32, &str>!
}

Part 4: Challenge - Option Revisited

Now that you understand generics, try to implement your own version of Option<T> from scratch (call it Maybe<T>). Implement is_some(), is_none(), unwrap(), and unwrap_or() methods.