IS 4010: Application Development with Artificial Intelligence

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 in more depth

  • 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

Where they appear:

  • Rust standard library: Built on generics (Vec<T>, Option<T>, Result<T, E>)
  • Code reuse: Write once, use with any type
  • Type checking: Trait constraints are checked at compile time
  • Static abstraction: Generic code can be specialized without dynamic dispatch

Library 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
}

Try it yourself: Run in Rust Playground

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);

Try it yourself: Run in Rust Playground

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 };
}

Try it yourself: Run in Rust Playground

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
}

Try it yourself: Run in Rust Playground

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),
}

Try it yourself: Run in Rust Playground

Usage:

fn validate_username(name: &str) -> Result<&str, String> {
    if name.trim().is_empty() {
        Err(String::from("Username cannot be empty"))
    } else {
        Ok(name)
    }
}

Try it yourself: Run in Rust Playground

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()
    }
}

Try it yourself: Run in Rust Playground

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)
    }
}

Try it yourself: Run in Rust Playground

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
}

Try it yourself: Run in Rust Playground

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
}

Try it yourself: Run in Rust Playground

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

Cannot derive: Display, Iterator (must implement manually)

Rust Book: Derivable Traits

Implementing the 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)
}

Try it yourself: Run in Rust Playground

Rust Book: Display Trait

Implementing the 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
        }
    }
}

Try it yourself: Run in Rust Playground

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> {
        todo!("construct an empty stack")
    }

    fn push(&mut self, item: T) {
        todo!("place item on top")
    }

    fn pop(&mut self) -> Option<T> {
        todo!("remove and return the top item")
    }

    fn is_empty(&self) -> bool {
        todo!("report whether the stack has no items")
    }
}

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 in more depth

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: Read and write trait implementations, bounds, and generic interfaces

Lab 12 preview: generic stack

What you’ll build:

  • Generic Stack<T> data structure
  • Implement core methods: new, push, pop, peek, is_empty, len
  • Implement Display trait for custom formatting
  • Use the supplied test suite without modifying it

Learning goals:

  • Apply generics to create reusable data structures
  • Implement standard library traits
  • Understand trait bounds in practice
  • Read tests for generic types and use their failures as evidence

Lab 12 workflow

Deliverable: Complete the todo!() implementations in the supplied Stack<T> project without modifying the test module.

cargo test
cargo fmt
cargo clippy -- -D warnings
git add week12/
git commit -m "Complete Lab 12"
git push origin main

Confirm the Week 12 README badge is green; that badge is the complete 10-point grade. See week12/lab12.md.

Where traits and generics are used

Why these skills matter:

Generics: - Foundation of modern language features (Java, C#, TypeScript, Swift, Go) - Reusable library and application interfaces - Constraints and bounds that document supported operations

Traits/Interfaces: - Interface design skills transfer to all languages - Composition as an alternative to inheritance hierarchies - Extensible interfaces and selected plugin architectures - Explicit reasoning about static and dynamic dispatch

Questions for review: - “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 - Static dispatch can avoid a virtual-call cost; measure the complete program when performance matters - 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 and traits support reusable abstractions with compile-time constraints - Type safety at compile time - Explicit reusable interfaces with static or dynamic dispatch choices - Patterns used throughout Rust’s standard library and ecosystem

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: idiomatic Rust

Week 13 topics:

  • Iterators and closures
  • Smart pointers: Box, Rc, and RefCell
  • Idiomatic error handling with Result
  • A new Lab 13 assignment

Why it matters: These patterns help express reusable behavior while preserving Rust’s compile-time checks.

Focus: Transfer this week’s trait and generic foundations into idiomatic Rust patterns.

Questions?

Get help:

  • Office hours: Schedule on Teams
  • Course discussion: Microsoft Teams channel
  • AI tools: browser chat, GitHub Copilot, Copilot CLI, or Antigravity CLI
  • Rust community: users.rust-lang.org

This week’s materials:

Start Lab 12

Follow the due date shown in Canvas.

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. Run the supplied tests, formatter check, and Clippy
  6. Ask questions early and often

Remember: A green Week 12 badge represents the complete 10-point lab.

Good luck!