077 — Generic Bounds
Tutorial
The Problem
Generic bounds constrain which types a generic function or struct can be used with. fn print_list<T: Display>(items: &[T]) works for any T that implements Display. Without bounds, generic code cannot call any methods on T. With bounds, you unlock the full interface of the trait.
Bounds are Rust's mechanism for "bounded polymorphism" — the type theory concept underlying Java interfaces, Haskell typeclasses, and OCaml module signatures. They enable writing algorithms once that work across many types, with the compiler verifying type safety and monomorphizing for performance.
🎯 Learning Outcomes
T: Display+: T: Display + CloneT: std::iter::Sum + CopyCode Example
#![allow(clippy::all)]
// 077: Generic Bounds
// Constraining generic types with trait bounds
use std::fmt::Display;
// Approach 1: Single bound
fn print_item<T: Display>(item: &T) -> String {
format!("{}", item)
}
fn print_list<T: Display>(items: &[T]) -> String {
let strs: Vec<String> = items.iter().map(|x| format!("{}", x)).collect();
format!("[{}]", strs.join(", "))
}
// Approach 2: Multiple bounds
fn print_and_clone<T: Display + Clone>(item: &T) -> (String, T) {
(format!("{}", item), item.clone())
}
fn find_max<T: PartialOrd + Clone>(items: &[T]) -> Option<T> {
items
.iter()
.cloned()
.reduce(|a, b| if a >= b { a } else { b })
}
// Approach 3: Bounds for arithmetic
fn sum_items<T: std::iter::Sum + Copy>(items: &[T]) -> T {
items.iter().copied().sum()
}
fn contains<T: PartialEq>(items: &[T], target: &T) -> bool {
items.iter().any(|x| x == target)
}
// Generic pair with bounds
fn larger<T: PartialOrd>(a: T, b: T) -> T {
if a >= b {
a
} else {
b
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_print_item() {
assert_eq!(print_item(&42), "42");
assert_eq!(print_item(&"hello"), "hello");
}
#[test]
fn test_print_list() {
assert_eq!(print_list(&[1, 2, 3]), "[1, 2, 3]");
}
#[test]
fn test_print_and_clone() {
let (s, v) = print_and_clone(&42);
assert_eq!(s, "42");
assert_eq!(v, 42);
}
#[test]
fn test_find_max() {
assert_eq!(find_max(&[3, 1, 4, 1, 5]), Some(5));
assert_eq!(find_max::<i32>(&[]), None);
}
#[test]
fn test_contains() {
assert!(contains(&[1, 2, 3], &2));
assert!(!contains(&[1, 2, 3], &4));
}
#[test]
fn test_larger() {
assert_eq!(larger(10, 20), 20);
assert_eq!(larger("z", "a"), "z");
}
}Key Differences
T: Display finds the implementation automatically. OCaml passes trait dictionaries (module arguments) explicitly in the traditional style.T: A + B + C is compact. OCaml's functor approach requires a module combining all required interfaces.where clause**: where T: Display + Clone is equivalent to the inline bound but more readable for complex constraints. Both compile to the same code.print_item::<i32> and print_item::<String> are separate compiled functions. OCaml uses boxing for polymorphism (no monomorphization by default).OCaml Approach
OCaml passes trait "dictionaries" explicitly as module or function arguments:
(* Explicit dictionary passing — the function receives the "show" implementation *)
let print_item (type a) (show : a -> string) (x : a) = print_string (show x)
(* Module functor approach — equivalent to Rust's generic bounds *)
module type ORDERED = sig
type t
val compare : t -> t -> int
end
module FindMax(O : ORDERED) = struct
let find_max lst =
List.fold_left
(fun acc x -> if O.compare x acc > 0 then x else acc)
(List.hd lst) (List.tl lst)
end
OCaml's modular implicits (a research extension) would make this automatic, similar to Rust's trait resolution.
Full Source
#![allow(clippy::all)]
// 077: Generic Bounds
// Constraining generic types with trait bounds
use std::fmt::Display;
// Approach 1: Single bound
fn print_item<T: Display>(item: &T) -> String {
format!("{}", item)
}
fn print_list<T: Display>(items: &[T]) -> String {
let strs: Vec<String> = items.iter().map(|x| format!("{}", x)).collect();
format!("[{}]", strs.join(", "))
}
// Approach 2: Multiple bounds
fn print_and_clone<T: Display + Clone>(item: &T) -> (String, T) {
(format!("{}", item), item.clone())
}
fn find_max<T: PartialOrd + Clone>(items: &[T]) -> Option<T> {
items
.iter()
.cloned()
.reduce(|a, b| if a >= b { a } else { b })
}
// Approach 3: Bounds for arithmetic
fn sum_items<T: std::iter::Sum + Copy>(items: &[T]) -> T {
items.iter().copied().sum()
}
fn contains<T: PartialEq>(items: &[T], target: &T) -> bool {
items.iter().any(|x| x == target)
}
// Generic pair with bounds
fn larger<T: PartialOrd>(a: T, b: T) -> T {
if a >= b {
a
} else {
b
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_print_item() {
assert_eq!(print_item(&42), "42");
assert_eq!(print_item(&"hello"), "hello");
}
#[test]
fn test_print_list() {
assert_eq!(print_list(&[1, 2, 3]), "[1, 2, 3]");
}
#[test]
fn test_print_and_clone() {
let (s, v) = print_and_clone(&42);
assert_eq!(s, "42");
assert_eq!(v, 42);
}
#[test]
fn test_find_max() {
assert_eq!(find_max(&[3, 1, 4, 1, 5]), Some(5));
assert_eq!(find_max::<i32>(&[]), None);
}
#[test]
fn test_contains() {
assert!(contains(&[1, 2, 3], &2));
assert!(!contains(&[1, 2, 3], &4));
}
#[test]
fn test_larger() {
assert_eq!(larger(10, 20), 20);
assert_eq!(larger("z", "a"), "z");
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_print_item() {
assert_eq!(print_item(&42), "42");
assert_eq!(print_item(&"hello"), "hello");
}
#[test]
fn test_print_list() {
assert_eq!(print_list(&[1, 2, 3]), "[1, 2, 3]");
}
#[test]
fn test_print_and_clone() {
let (s, v) = print_and_clone(&42);
assert_eq!(s, "42");
assert_eq!(v, 42);
}
#[test]
fn test_find_max() {
assert_eq!(find_max(&[3, 1, 4, 1, 5]), Some(5));
assert_eq!(find_max::<i32>(&[]), None);
}
#[test]
fn test_contains() {
assert!(contains(&[1, 2, 3], &2));
assert!(!contains(&[1, 2, 3], &4));
}
#[test]
fn test_larger() {
assert_eq!(larger(10, 20), 20);
assert_eq!(larger("z", "a"), "z");
}
}
Deep Comparison
Core Insight
Rust generics require explicit bounds: <T: Display> means "T must implement Display". OCaml generics are unconstrained — any type works if the operations typecheck structurally.
OCaml Approach
'a is universally polymorphic — no constraintsRust Approach
<T: Trait> syntax for inline bounds<T: Display + Clone>Comparison Table
| Feature | OCaml | Rust |
|---|---|---|
| Syntax | 'a (unconstrained) | <T: Bound> |
| Multiple | N/A | T: A + B |
| Required? | No | Yes, to use methods |
| Checked | At use site | At declaration |
Exercises
median<T: PartialOrd + Clone>(mut v: Vec<T>) -> Option<T> that sorts the vector and returns the middle element. What additional bound is needed for sorting?min_max<T: PartialOrd + Clone>(v: &[T]) -> Option<(T, T)> that returns both the minimum and maximum in a single pass. Use fold with a (T, T) accumulator.Stats<T> that stores count, sum, min, max with bounds T: PartialOrd + Add + Copy + Default. Implement update(&mut self, x: T) that incorporates a new value.