Week 12: Generics, Traits, and Testing
Session 1: Generics and Traits
Session 2: Testing in Rust
Real-world impact:
Vec<T>, Option<T>, Result<T, E>)Industry examples:
Without generics, you need duplicate code:
Problem: Same logic, different types → code duplication!
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.
Define structs that work with any type:
Multiple type parameters:
You’ve been using generic enums all along:
Usage:
Methods on generic structs:
Notice: impl<T> declares the generic, then Point<T> uses it.
Traits define shared behavior:
Think of traits as: - Interfaces (Java/C#) - Protocols (Swift) - Type classes (Haskell)
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
}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
}where ClauseMake complex trait bounds readable:
Use where when: - Multiple trait bounds - Complex generic relationships - Improves readability
Return types that implement traits:
Limitation: Can only return ONE concrete type:
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 bitsDisplay: Format with {} (implement manually)PartialEq: Compare with == and !=Eq: Full equivalence relationPartialOrd: Compare with <, >, <=, >=Ord: Total orderingIterator: Types that can be iterated overAutomatically implement common traits:
Can derive: - Debug, Clone, Copy - PartialEq, Eq, PartialOrd, Ord - Hash, Default
Cannot derive: Display, Iterator (must implement manually)
Custom formatting for user-facing output:
Make your types iterable:
Building a generic data structure:
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?”
Industry perspective:
Benefits:
Rust advantage: Excellent built-in testing tools!
The TDD cycle:
// 1. RED: Write test first
#[test]
fn test_stack_push_pop() {
let mut stack = Stack::new();
stack.push(42);
assert_eq!(stack.pop(), Some(42));
}
// 2. GREEN: Implement to pass
impl<T> Stack<T> {
fn push(&mut self, item: T) { /* implement */ }
fn pop(&mut self) -> Option<T> { /* implement */ }
}
// 3. REFACTOR: Improve while tests passAnatomy of a Rust test:
#[cfg(test)]
mod tests {
use super::*; // Import from parent module
#[test]
fn test_addition() {
let result = 2 + 2;
assert_eq!(result, 4);
}
#[test]
fn test_stack_empty() {
let stack: Stack<i32> = Stack::new();
assert!(stack.is_empty());
}
#[test]
#[should_panic]
fn test_divide_by_zero() {
let _ = 1 / 0; // Should panic
}
}Built-in assertion tools:
Common patterns:
assert!: Boolean conditionassert_eq!: Equalityassert_ne!: InequalityTest that code panics correctly:
Testing Result errors:
Cargo test commands:
# Run all tests
cargo test
# Run tests in parallel (default)
cargo test
# Run tests sequentially
cargo test -- --test-threads=1
# Run specific test
cargo test test_stack_push
# Run tests matching pattern
cargo test stack
# Show println! output
cargo test -- --nocapture
# Run tests and show success output
cargo test -- --show-outputUnit tests (in same file as code):
Integration tests (in tests/ directory):
Rust allows testing private functions:
Philosophy: Tests are part of your code, so they can access private items.
Measure how much code is tested:
Industry standards: - 80%+ coverage is good - 90%+ coverage is excellent - 100% coverage is often overkill
Code examples in docs are automatically tested:
Run with:
Benefits: Ensures documentation examples always work!
Measure performance (nightly Rust):
Alternative: criterion (stable Rust):
GitHub Actions for automatic testing:
Benefits: - Tests run on every commit - Catch issues before merge - Enforce code quality standards
Write good tests:
test_stack_pop_empty_returns_noneBad test smells: - Tests that sometimes fail (“flaky tests”) - Tests that depend on external state - Tests that take too long
Lab 12 preview - TDD workflow:
// 1. Write test (RED)
#[test]
fn test_new_stack_is_empty() {
let stack: Stack<i32> = Stack::new();
assert!(stack.is_empty());
}
// 2. Implement (GREEN)
impl<T> Stack<T> {
fn new() -> Stack<T> {
Stack { items: Vec::new() }
}
fn is_empty(&self) -> bool {
self.items.is_empty()
}
}
// 3. Run tests - should pass!
// 4. Refactor if needed
// 5. Repeat for next featureTest strategy: - “What tests should I write for this generic Stack implementation?” - “Help me design a test suite for error handling in my Result-returning functions” - “What edge cases should I test for this string parsing function?”
Writing tests: - “Generate tests for this function: [paste code]” - “How do I test functions that return Result?” - “Help me write a test for this panic case”
Debugging tests: - “Why is my test failing? [paste test and code]” - “How do I test private functions in Rust?” - “My tests are flaky - what could cause non-deterministic behavior?”
TDD workflow: - “Walk me through implementing this feature using TDD” - “I have this test [paste] - what’s the minimum code to make it pass?”
Industry practices:
Rust compiler: - 100,000+ tests - Tests run on every pull request - Multiple test suites (unit, integration, ui tests)
Servo browser engine: - Comprehensive test coverage - Performance benchmarks - Crash testing and fuzzing
Tokio async runtime: - Property-based testing with proptest - Loom for concurrency testing - Miri for undefined behavior detection
Test properties that should always hold:
Tools: - quickcheck: Random input generation - proptest: Shrinking failed cases
What you’ll build:
Stack<T> data structurepush, pop, peek, is_empty, lenDisplay and Iterator traitsLearning goals:
Deliverable: Fully tested generic stack with 15+ tests!
Why these skills matter:
Generics: - Foundation of modern language features (Java, C#, TypeScript, Swift) - Essential for library and framework development - Interview questions: “Design a generic data structure”
Traits: - Interface design skills transfer to all languages - Composition over inheritance (modern best practice) - Understanding trait bounds helps with type systems
Testing: - Expected in all professional development - TDD is an industry standard practice - Testing skills demonstrate code quality awareness
Interview examples: - “Explain the difference between generics and dynamic dispatch” - “How would you test this function?” - “Design an interface for [problem] - what methods would it have?”
Generics: - Enable code reuse without sacrificing type safety - Zero-cost abstractions - no runtime overhead - Work with structs, enums, functions, and methods
Traits: - Define shared behavior across types - Enable polymorphism without inheritance - Foundation of Rust’s standard library
Testing: - Built-in with cargo test - TDD leads to better design - Comprehensive tests enable confident refactoring
Together: Generics + Traits + Tests = Robust, reusable Rust code
Official documentation: - The Rust Book - Chapter 10: Generic Types, Traits, and Lifetimes - The Rust Book - Chapter 11: Writing Automated Tests - Rust by Example: Generics - Rust by Example: Testing
Articles and guides: - Rust Traits: A Deep Dive - Effective Testing in Rust - Generic Associated Types
Tools: - Rust Playground - Test code online - cargo-tarpaulin - Code coverage - proptest - Property-based testing
Week 13 topics:
Why it matters: Move from “it works” to “it’s idiomatic Rust”
Lab 13: Refactor code to use idiomatic Rust patterns
Get help:
This week’s materials:
Lab due: Sunday, November 16, 2025, at 11:59 PM
What to do:
Remember: You’re not just learning Rust - you’re learning universal programming concepts that apply everywhere!
Good luck! 🦀
IS4010: App Development with AI