IS4010: AI-Enhanced Application Development

Week 12: Generics and Traits

Brandon M. Greenwell

Week 12 Overview

Session 1: Generic Types

  • Understanding the problem: code duplication
  • Generic functions and structs
  • Generic enums (Option, Result)
  • Generic methods and implementations
  • Introduction to trait bounds

Session 2: Traits Deep Dive

  • Trait definitions and implementations
  • Default implementations and trait parameters
  • Trait bounds and where clauses
  • Common traits: Debug, Clone, Display, Iterator
  • Implementing traits for custom types

Why Generics and Traits Matter

Real-world impact:

  • Rust standard library: Built on generics (Vec<T>, Option<T>, Result<T, E>)
  • Code reuse: Write once, use with any type
  • Type safety: Compile-time guarantees without runtime cost
  • Zero-cost abstractions: No performance penalty

Industry examples:

  • Servo: Mozilla’s parallel browser engine
  • Tokio: Async runtime using generic futures
  • Serde: Generic serialization framework

Session 1: Generics and Traits

Understanding the Problem

Without generics, you need duplicate code:

fn largest_i32(list: &[i32]) -> &i32 {
    let mut largest = &list[0];
    for item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

fn largest_f64(list: &[f64]) -> &f64 {
    let mut largest = &list[0];
    for item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

Problem: Same logic, different types → code duplication!

Introducing Generics

Generic functions work with multiple types:

fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];
    for item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

// Works with any type that can be compared!
let numbers = vec![34, 50, 25, 100, 65];
let result = largest(&numbers);

let chars = vec!['y', 'm', 'a', 'q'];
let result = largest(&chars);

Key concept: T: PartialOrd is a trait bound - T must implement PartialOrd.

📖 Rust Book: Generic Data Types

Generic Structs

Define structs that work with any type:

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

fn main() {
    let integer_point = Point { x: 5, y: 10 };
    let float_point = Point { x: 1.0, y: 4.0 };
}

Multiple type parameters:

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

fn main() {
    let mixed = Point { x: 5, y: 4.0 };  // T=i32, U=f64
}

📖 Rust Book: Generic Structs

Generic Enums

You’ve been using generic enums all along:

// Option<T> from standard library
enum Option<T> {
    Some(T),
    None,
}

// Result<T, E> for error handling
enum Result<T, E> {
    Ok(T),
    Err(E),
}

Usage:

fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err(String::from("Division by zero"))
    } else {
        Ok(a / b)
    }
}

📖 Rust Book: Generic Enums

Generic Methods

Methods on generic structs:

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

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

// Method only for specific type
impl Point<f32> {
    fn distance_from_origin(&self) -> f32 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

Notice: impl<T> declares the generic, then Point<T> uses it.

📖 Rust Book: Generic Methods

What Are Traits?

Traits define shared behavior:

pub trait Summary {
    fn summarize(&self) -> String;
}

struct NewsArticle {
    headline: String,
    content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}: {}", self.headline, self.content)
    }
}

Think of traits as: - Interfaces (Java/C#) - Protocols (Swift) - Type classes (Haskell)

📖 Rust Book: Traits

Default Trait Implementations

Traits can provide default behavior:

pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }
}

struct Tweet {
    username: String,
    content: String,
}

impl Summary for Tweet {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
    // summarize() uses the default implementation
}

📖 Rust Book: Default Implementations

Traits as Parameters

Accept any type implementing a trait:

pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

// Longer syntax (trait bound):
pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

// Multiple trait bounds:
pub fn notify<T: Summary + Display>(item: &T) {
    // Can call summarize() and also use {} formatting
}

📖 Rust Book: Traits as Parameters

The where Clause

Make complex trait bounds readable:

// Hard to read:
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
    // ...
}

// Much clearer with where clause:
fn some_function<T, U>(t: &T, u: &U) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{
    // ...
}

Use where when: - Multiple trait bounds - Complex generic relationships - Improves readability

📖 Rust Book: where Clauses

Returning Trait Types

Return types that implement traits:

fn returns_summarizable() -> impl Summary {
    Tweet {
        username: String::from("rustacean"),
        content: String::from("Rust is awesome!"),
    }
}

Limitation: Can only return ONE concrete type:

// ❌ ERROR: Can't return different types
fn returns_summarizable(switch: bool) -> impl Summary {
    if switch {
        NewsArticle { /* ... */ }
    } else {
        Tweet { /* ... */ }  // Error!
    }
}

📖 Rust Book: Returning Traits

Common Standard Library Traits

Frequently used traits you should know:

  • Debug: Format with {:?} (derive with #[derive(Debug)])
  • Clone: Create deep copies with .clone()
  • Copy: Types that can be copied by just copying bits
  • Display: Format with {} (implement manually)
  • PartialEq: Compare with == and !=
  • Eq: Full equivalence relation
  • PartialOrd: Compare with <, >, <=, >=
  • Ord: Total ordering
  • Iterator: Types that can be iterated over

📖 Rust Standard Library Traits

Deriving Traits

Automatically implement common traits:

#[derive(Debug, Clone, PartialEq, Eq)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = p1.clone();

    println!("{:?}", p1);  // Debug
    assert_eq!(p1, p2);    // PartialEq
}

Can derive: - Debug, Clone, Copy - PartialEq, Eq, PartialOrd, Ord - Hash, Default

Cannot derive: Display, Iterator (must implement manually)

📖 Rust Book: Derivable Traits

Implementing Display Trait

Custom formatting for user-facing output:

use std::fmt;

struct Point {
    x: i32,
    y: i32,
}

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

fn main() {
    let p = Point { x: 1, y: 2 };
    println!("Point: {}", p);  // Point: (1, 2)
}

📖 Rust Book: Display Trait

Implementing Iterator Trait

Make your types iterable:

struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}

impl Iterator for Counter {
    type Item = u32;  // Associated type

    fn next(&mut self) -> Option<Self::Item> {
        if self.count < 5 {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }
}

📖 Rust Book: Iterator Trait

Generic Stack Example

Building a generic data structure:

struct Stack<T> {
    items: Vec<T>,
}

impl<T> Stack<T> {
    fn new() -> Stack<T> {
        Stack { items: Vec::new() }
    }

    fn push(&mut self, item: T) {
        self.items.push(item);
    }

    fn pop(&mut self) -> Option<T> {
        self.items.pop()
    }

    fn is_empty(&self) -> bool {
        self.items.is_empty()
    }
}

🎮 Try it in Playground

🤖 AI Prompting: Generics and Traits

Design assistance: - “Help me design a generic cache data structure in Rust” - “What traits should my custom type implement for common operations?” - “Explain when to use trait bounds vs where clauses”

Implementation help: - “How do I implement the Iterator trait for my custom struct?” - “Why can’t I return different types from a function returning impl Trait?” - “Help me fix this trait bound error: [paste error]”

Code review: - “Is this the idiomatic way to use generics in Rust?” - “Should this be a generic function or use dynamic dispatch?” - “How can I make these trait bounds more readable?”

Session 2: Traits Deep Dive

Traits: Defining Shared Behavior

What we’ll cover:

  • Trait definitions and custom traits
  • Implementing traits for your types
  • Default implementations for code reuse
  • Traits as function parameters
  • Advanced trait bounds with where clauses
  • Common standard library traits

Goal: Master Rust’s powerful trait system for polymorphism and code reuse

Lab 12 Preview: Generic Stack

What you’ll build:

  • Generic Stack<T> data structure
  • Implement core methods: push, pop, peek, is_empty, len
  • Implement Display trait for custom formatting
  • Implement Iterator trait to make your stack iterable
  • Comprehensive test suite with 15+ tests

Learning goals:

  • Apply generics to create reusable data structures
  • Implement standard library traits
  • Understand trait bounds in practice
  • Write tests for generic types

Deliverable: A fully functional, well-tested generic stack!

Career Relevance

Why these skills matter:

Generics: - Foundation of modern language features (Java, C#, TypeScript, Swift, Go) - Essential for library and framework development - Interview questions: “Design a generic data structure” - Understanding constraints and bounds

Traits/Interfaces: - Interface design skills transfer to all languages - Composition over inheritance (modern best practice) - Critical for extensible systems and plugin architectures - Type system understanding for advanced roles

Real interview questions: - “Explain the difference between generics and dynamic dispatch” - “Design an interface for [problem] - what methods would it have?” - “How would you make this code reusable across different types?” - “What are the tradeoffs between compile-time and runtime polymorphism?”

Key Takeaways

Generics: - Enable code reuse without sacrificing type safety - Zero-cost abstractions - no runtime overhead - Work with structs, enums, functions, and methods - Use trait bounds to constrain type parameters

Traits: - Define shared behavior across types - Enable polymorphism without inheritance - Foundation of Rust’s standard library - Can have default implementations for convenience

Together: - Generics + Traits = Powerful, reusable abstractions - Type safety at compile time - Expressive code that’s still performant - Industry-standard design patterns

Additional Resources

Official documentation: - The Rust Book - Chapter 10: Generic Types, Traits, and Lifetimes - Rust by Example: Generics - Rust by Example: Traits - Rust Standard Library Traits

Articles and guides: - Rust Traits: A Deep Dive - Generic Associated Types - Tour of Rust’s Standard Library Traits

Tools: - Rust Playground - Test code online - docs.rs - Browse crate documentation

Next Week: Testing and Packaging

Week 13 topics:

  • Test-driven development (TDD) workflow
  • Unit testing vs integration testing
  • Error handling with Result and Option
  • Packaging Rust applications
  • Building command-line tools

Why it matters: Learn to build complete, tested, distributable applications

Focus: Final project work begins - no new lab assignments

Questions?

Get help:

This week’s materials:

Start Lab 12!

Lab due: Sunday, November 23, 2025, at 11:59 PM

What to do:

  1. Review lecture materials and examples
  2. Work through interactive exercises
  3. Begin Lab 12: Generic Stack implementation
  4. Apply generics and traits from this week
  5. Write comprehensive tests for your implementation
  6. Ask questions early and often

Remember: You’re not just learning Rust - you’re learning universal programming concepts that apply everywhere!

Good luck! 🦀