Skip to content

Rust Generics & Traits Best Practices

Objective: Master senior-level Rust generics and traits patterns for production systems. When you need to build reusable, type-safe code, when you want to leverage Rust's type system, when you need enterprise-grade generic programmingโ€”these best practices become your weapon of choice.

Core Principles

  • Type Safety: Leverage generics for compile-time type checking
  • Code Reuse: Write generic code that works with multiple types
  • Trait Bounds: Use trait bounds to constrain generic types
  • Zero-Cost Abstractions: Generics have no runtime overhead
  • Composition: Build complex behavior from simple traits

Generic Patterns

Basic Generics

// rust/01-basic-generics.rs

/*
Basic generic patterns and best practices for Rust
*/

use std::fmt::Display;
use std::cmp::PartialOrd;

/// Generic data structure.
pub struct Container<T> {
    data: T,
}

impl<T> Container<T> {
    pub fn new(data: T) -> Self {
        Self { data }
    }

    pub fn get(&self) -> &T {
        &self.data
    }

    pub fn get_mut(&mut self) -> &mut T {
        &mut self.data
    }

    pub fn into_inner(self) -> T {
        self.data
    }
}

/// Generic function with trait bounds.
pub fn find_max<T>(items: &[T]) -> Option<&T>
where
    T: PartialOrd,
{
    items.iter().max_by(|a, b| a.partial_cmp(b).unwrap())
}

/// Generic function with multiple trait bounds.
pub fn print_and_return<T>(item: T) -> T
where
    T: Display + Clone,
{
    println!("Item: {}", item);
    item
}

/// Generic struct with trait bounds.
pub struct SortedContainer<T>
where
    T: PartialOrd + Clone,
{
    items: Vec<T>,
}

impl<T> SortedContainer<T>
where
    T: PartialOrd + Clone,
{
    pub fn new() -> Self {
        Self { items: Vec::new() }
    }

    pub fn insert(&mut self, item: T) {
        self.items.push(item);
        self.items.sort_by(|a, b| a.partial_cmp(b).unwrap());
    }

    pub fn get_items(&self) -> &[T] {
        &self.items
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_container() {
        let container = Container::new(42);
        assert_eq!(*container.get(), 42);
    }

    #[test]
    fn test_find_max() {
        let numbers = vec![1, 5, 3, 9, 2];
        assert_eq!(find_max(&numbers), Some(&9));
    }

    #[test]
    fn test_sorted_container() {
        let mut container = SortedContainer::new();
        container.insert(3);
        container.insert(1);
        container.insert(2);

        let items = container.get_items();
        assert_eq!(items, &[1, 2, 3]);
    }
}

Trait Patterns

// rust/02-trait-patterns.rs

/*
Trait patterns and best practices for Rust
*/

use std::fmt::Display;

/// Basic trait definition.
pub trait Drawable {
    fn draw(&self);
    fn area(&self) -> f64;
}

/// Trait with default implementation.
pub trait DrawableWithDefault {
    fn draw(&self) {
        println!("Drawing default shape");
    }

    fn area(&self) -> f64;

    fn perimeter(&self) -> f64 {
        0.0
    }
}

/// Trait with associated types.
pub trait Iterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;
}

/// Trait with generic parameters.
pub trait Processor<T> {
    fn process(&self, item: T) -> T;
}

/// Trait with lifetime parameters.
pub trait Parser<'a> {
    fn parse(&self, input: &'a str) -> Result<&'a str, &'a str>;
}

/// Trait with multiple bounds.
pub trait ComplexTrait: Display + Clone + PartialEq {
    fn complex_method(&self) -> String;
}

/// Trait with generic associated types.
pub trait Container {
    type Item;
    type Iterator<'a>: Iterator<Item = &'a Self::Item>
    where
        Self: 'a;

    fn iter(&self) -> Self::Iterator<'_>;
}

/// Demonstrates trait implementation.
pub struct Circle {
    radius: f64,
}

impl Drawable for Circle {
    fn draw(&self) {
        println!("Drawing circle with radius {}", self.radius);
    }

    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
}

impl DrawableWithDefault for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }

    fn perimeter(&self) -> f64 {
        2.0 * std::f64::consts::PI * self.radius
    }
}

impl Display for Circle {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Circle(radius: {})", self.radius)
    }
}

impl Clone for Circle {
    fn clone(&self) -> Self {
        Self { radius: self.radius }
    }
}

impl PartialEq for Circle {
    fn eq(&self, other: &Self) -> bool {
        (self.radius - other.radius).abs() < 1e-10
    }
}

impl ComplexTrait for Circle {
    fn complex_method(&self) -> String {
        format!("Complex circle: {}", self)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_circle_drawable() {
        let circle = Circle { radius: 5.0 };
        circle.draw();
        assert!((circle.area() - 78.54).abs() < 1.0);
    }

    #[test]
    fn test_circle_display() {
        let circle = Circle { radius: 5.0 };
        let display = format!("{}", circle);
        assert!(display.contains("Circle"));
    }
}

Advanced Generic Patterns

// rust/03-advanced-generics.rs

/*
Advanced generic patterns and best practices for Rust
*/

use std::marker::PhantomData;
use std::ops::{Add, Mul};

/// Generic function with complex bounds.
pub fn process_items<T, U, F>(items: &[T], processor: F) -> Vec<U>
where
    T: Clone,
    F: Fn(T) -> U,
{
    items.iter().map(|item| processor(item.clone())).collect()
}

/// Generic struct with phantom data.
pub struct PhantomContainer<T> {
    data: Vec<u8>,
    _phantom: PhantomData<T>,
}

impl<T> PhantomContainer<T> {
    pub fn new() -> Self {
        Self {
            data: Vec::new(),
            _phantom: PhantomData,
        }
    }

    pub fn add_data(&mut self, data: u8) {
        self.data.push(data);
    }

    pub fn get_data(&self) -> &[u8] {
        &self.data
    }
}

/// Generic trait with associated types.
pub trait Converter {
    type Input;
    type Output;

    fn convert(&self, input: Self::Input) -> Self::Output;
}

/// Generic struct implementing Converter.
pub struct StringToIntConverter;

impl Converter for StringToIntConverter {
    type Input = String;
    type Output = i32;

    fn convert(&self, input: String) -> i32 {
        input.parse().unwrap_or(0)
    }
}

/// Generic function with trait bounds.
pub fn calculate<T>(a: T, b: T) -> T
where
    T: Add<Output = T> + Mul<Output = T> + Copy,
{
    a * b + a + b
}

/// Generic struct with multiple type parameters.
pub struct Pair<T, U> {
    first: T,
    second: U,
}

impl<T, U> Pair<T, U> {
    pub fn new(first: T, second: U) -> Self {
        Self { first, second }
    }

    pub fn get_first(&self) -> &T {
        &self.first
    }

    pub fn get_second(&self) -> &U {
        &self.second
    }
}

/// Generic enum.
pub enum Result<T, E> {
    Ok(T),
    Err(E),
}

impl<T, E> Result<T, E> {
    pub fn is_ok(&self) -> bool {
        matches!(self, Result::Ok(_))
    }

    pub fn is_err(&self) -> bool {
        matches!(self, Result::Err(_))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_process_items() {
        let numbers = vec![1, 2, 3, 4, 5];
        let doubled: Vec<i32> = process_items(&numbers, |x| x * 2);
        assert_eq!(doubled, vec![2, 4, 6, 8, 10]);
    }

    #[test]
    fn test_phantom_container() {
        let mut container: PhantomContainer<String> = PhantomContainer::new();
        container.add_data(42);
        assert_eq!(container.get_data(), &[42]);
    }

    #[test]
    fn test_converter() {
        let converter = StringToIntConverter;
        let result = converter.convert("123".to_string());
        assert_eq!(result, 123);
    }

    #[test]
    fn test_calculate() {
        let result = calculate(2, 3);
        assert_eq!(result, 11); // 2*3 + 2 + 3 = 11
    }

    #[test]
    fn test_pair() {
        let pair = Pair::new("hello", 42);
        assert_eq!(*pair.get_first(), "hello");
        assert_eq!(*pair.get_second(), 42);
    }
}

TL;DR Runbook

Quick Start

// 1. Basic generics
struct Container<T> {
    data: T,
}

impl<T> Container<T> {
    fn new(data: T) -> Self {
        Self { data }
    }
}

// 2. Trait bounds
fn find_max<T>(items: &[T]) -> Option<&T>
where
    T: PartialOrd,
{
    items.iter().max_by(|a, b| a.partial_cmp(b).unwrap())
}

// 3. Trait definition
trait Drawable {
    fn draw(&self);
    fn area(&self) -> f64;
}

// 4. Trait implementation
impl Drawable for Circle {
    fn draw(&self) {
        println!("Drawing circle");
    }

    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
}

Essential Patterns

// Complete generics and traits setup
pub fn setup_rust_generics_traits() {
    // 1. Generic functions
    // 2. Generic structs
    // 3. Trait definitions
    // 4. Trait implementations
    // 5. Trait bounds
    // 6. Associated types
    // 7. Generic associated types
    // 8. Phantom data

    println!("Rust generics and traits setup complete!");
}

This guide provides the complete machinery for Rust generics and traits. Each pattern includes implementation examples, type system strategies, and real-world usage patterns for enterprise generic programming.