Lesson 31 of 94
Generics
Generics let you write code once for many types. Rust generics work through monomorphization: the compiler generates a separate, specialized copy of generic code for each concrete type used. Vec<i32> and Vec<String> are different types with different machine code, and generic code is as fast as hand-written code for each type.
That explains what Rust generics can do: work directly with primitives, call T::default() or T::new(), specialize methods for particular types, and use constant values as parameters.
| Concept | Rust |
|---|---|
| Generic function | fn first<T>(list: &[T]) -> &T |
| Bounded type parameter | <T: Ord> |
| Generic struct | struct Wrapper<T> { value: T } |
| Generic collection | Vec<i32> (unboxed, contiguous) |
Creating a T |
T::default() or T::new() via a trait bound |
| How generics compile | A specialized copy per type (monomorphization) |
1. The problem: duplicated code
Two functions with identical logic, differing only in type:
fn largest_i32(list: &[i32]) -> &i32 {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
fn largest_char(list: &[char]) -> &char {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
println!("{}", largest_i32(&[34, 50, 25, 100, 65]));
println!("{}", largest_char(&['y', 'm', 'a', 'q']));
}100
y2. A generic function needs to say what it requires
Replacing the types with a type parameter T is not enough: the compiler refuses to compile item > largest because not every type can be compared.
fn largest<T>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
println!("{}", largest(&[1, 2, 3]));
}error[E0369]: binary operation `>` cannot be applied to type `&T`
--> src/main.rs:4:17
|
4 | if item > largest {
| ---- ^ ------- &T
| |
| &T
|
help: consider restricting type parameter `T` with trait `PartialOrd`
|
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
| ++++++++++++++++++++++This is a key difference from C++ templates: Rust checks a generic function once, against its declared requirements, not separately for each type it is used with. Errors are reported where the generic code is written, and they're readable.
3. Trait bounds: T: PartialOrd
A trait bound says what a type parameter must be able to do. T: PartialOrd means "any type that supports < and >" (the trait from Deriving Standard Traits).
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
println!("{}", largest(&[34, 50, 25, 100, 65]));
println!("{}", largest(&['y', 'm', 'a', 'q']));
println!("{}", largest(&[1.5, -2.0, 0.25]));
println!("{}", largest(&["pear", "apple", "zucchini"]));
}100
y
1.5
zucchini4. Returning T by value needs Copy
To return the largest element by value rather than as a reference, the function must copy it out of the slice, which is only allowed if T: Copy (Move, Copy & Clone). Several bounds are combined with +.
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let numbers = vec![34, 50, 25, 100, 65];
println!("{}", largest(&numbers));
let chars = vec!['y', 'm', 'a', 'q'];
println!("{}", largest(&chars));
}100
yEvery piece of this signature has now been covered: generics, the trait bound PartialOrd, the Copy trait, the slice &[T], and the for &item pattern.
5. Without Copy, moving out of the slice fails
With Strings, which are not Copy, the by-value version doesn't compile, while the reference version from example 3 works fine.
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let words = vec![String::from("pear"), String::from("apple")];
println!("{}", largest(&words));
}error[E0277]: the trait bound `String: Copy` is not satisfied
--> src/main.rs:13:28
|
13 | println!("{}", largest(&words));
| ------- ^^^^^^ the trait `Copy` is not implemented for `String`
| |
| required by a bound introduced by this call
|
note: required by a bound in `largest`
--> src/main.rs:1:28
|
1 | fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
| ^^^^ required by this bound in `largest`Alternatives: return &T (example 3), require T: Clone and call .clone(), or take the vector by value and move the result out.
6. Generic structs
#[derive(Debug)]
struct Point<T> {
x: T,
y: T,
}
fn main() {
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
let labels = Point { x: "left", y: "top" };
println!("{:?} {:?} {:?}", integer, float, labels);
}Point { x: 5, y: 10 } Point { x: 1.0, y: 4.0 } Point { x: "left", y: "top" }7. One type parameter means one type
Point<T> has both fields of the same T, so mixing an integer and a float is an error.
struct Point<T> {
x: T,
y: T,
}
fn main() {
let p = Point { x: 5, y: 4.0 };
}error[E0308]: mismatched types
--> src/main.rs:7:30
|
7 | let p = Point { x: 5, y: 4.0 };
| ^^^ expected integer, found floating-point number8. Several type parameters
#[derive(Debug)]
struct Pair<K, V> {
key: K,
value: V,
}
fn main() {
let setting = Pair { key: "timeout_ms", value: 5_000 };
let flag = Pair { key: String::from("beta"), value: true };
let nested = Pair { key: 1, value: Pair { key: 'a', value: 2.5 } };
println!("{:?}\n{:?}\n{:?}", setting, flag, nested);
}Pair { key: "timeout_ms", value: 5000 }
Pair { key: "beta", value: true }
Pair { key: 1, value: Pair { key: 'a', value: 2.5 } }9. Methods on generic structs
impl<T> Point<T> declares T for the impl block, so the methods work for every Point<T>.
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn new(x: T, y: T) -> Self {
Self { x, y }
}
fn x(&self) -> &T {
&self.x
}
fn swap(self) -> Point<T> {
Point { x: self.y, y: self.x }
}
}
fn main() {
let p = Point::new("east", "north");
println!("x = {}", p.x());
let q = p.swap();
println!("after swap: ({}, {})", q.x, q.y);
}x = east
after swap: (north, east)10. Methods only for specific types
An impl block for a concrete type adds methods that exist only for that type.
struct Point<T> {
x: T,
y: T,
}
impl Point<f64> {
fn distance_from_origin(&self) -> f64 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
impl Point<&str> {
fn describe(&self) -> String {
format!("{} and {}", self.x, self.y)
}
}
fn main() {
let p = Point { x: 3.0, y: 4.0 };
println!("{}", p.distance_from_origin());
let d = Point { x: "up", y: "down" };
println!("{}", d.describe());
// Point { x: 3, y: 4 }.distance_from_origin(); // no such method on Point<i32>
}5
up and down11. Methods with their own type parameters
A method can introduce new type parameters in addition to the struct's.
#[derive(Debug)]
struct Point<X, Y> {
x: X,
y: Y,
}
impl<X, Y> Point<X, Y> {
fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X, Y2> {
Point { x: self.x, y: other.y }
}
}
fn main() {
let p1 = Point { x: 5, y: 10.4 };
let p2 = Point { x: "Hello", y: 'c' };
println!("{:?}", p1.mixup(p2));
}Point { x: 5, y: 'c' }12. Generic enums
Option<T> and Result<T, E> are generic enums. You can define your own the same way.
#[derive(Debug)]
enum Either<L, R> {
Left(L),
Right(R),
}
fn parse_id(input: &str) -> Either<u64, String> {
match input.parse::<u64>() {
Ok(n) => Either::Left(n),
Err(_) => Either::Right(input.to_lowercase()),
}
}
fn main() {
for input in ["1042", "Ferris", "7"] {
match parse_id(input) {
Either::Left(id) => println!("numeric id {id}"),
Either::Right(username) => println!("username {username}"),
}
}
}numeric id 1042
username ferris
numeric id 713. A generic recursive enum: a binary tree
#[derive(Debug)]
enum Tree<T> {
Leaf,
Node(Box<Tree<T>>, T, Box<Tree<T>>),
}
impl<T: PartialOrd> Tree<T> {
fn insert(self, value: T) -> Tree<T> {
match self {
Tree::Leaf => Tree::Node(Box::new(Tree::Leaf), value, Box::new(Tree::Leaf)),
Tree::Node(left, v, right) => {
if value < v {
Tree::Node(Box::new(left.insert(value)), v, right)
} else {
Tree::Node(left, v, Box::new(right.insert(value)))
}
}
}
}
fn in_order<'a>(&'a self, out: &mut Vec<&'a T>) {
if let Tree::Node(left, v, right) = self {
left.in_order(out);
out.push(v);
right.in_order(out);
}
}
}
fn main() {
let mut tree = Tree::Leaf;
for n in [50, 30, 70, 20, 40, 60] {
tree = tree.insert(n);
}
let mut sorted = Vec::new();
tree.in_order(&mut sorted);
println!("{:?}", sorted);
let mut words = Tree::Leaf;
for w in ["pear", "apple", "fig"] {
words = words.insert(w);
}
let mut out = Vec::new();
words.in_order(&mut out);
println!("{:?}", out);
}[20, 30, 40, 50, 60, 70]
["apple", "fig", "pear"]impl<T: PartialOrd> Tree<T> puts the bound on the whole impl block, so these methods exist only for trees of comparable values.
14. The turbofish: naming the type explicitly
When the compiler can't infer a type parameter, you give it with ::<Type>, nicknamed the turbofish. It is needed most with parse, collect, and functions whose type parameter only appears in the return type.
fn zero<T: Default>() -> T {
T::default()
}
fn main() {
let n = "42".parse::<u8>().unwrap();
let words = "a b c".split(' ').collect::<Vec<_>>();
let z = zero::<f64>();
let s = zero::<String>();
println!("{n} {:?} {z} {:?}", words, s);
}42 ["a", "b", "c"] 0 ""Vec<_> lets the compiler fill in the element type while you pick the collection.
15. When type inference needs help
fn main() {
let items = Vec::new();
println!("{}", items.len());
}error[E0282]: type annotations needed for `Vec<_>`
--> src/main.rs:2:9
|
2 | let items = Vec::new();
| ^^^^^ ---------- type must be known at this point
|
help: consider giving `items` an explicit type, where the type for type parameter `T` is specified
|
2 | let items: Vec<T> = Vec::new();
| ++++++++Nothing tells the compiler what the vector holds. An annotation (let items: Vec<String> = Vec::new();), a turbofish (Vec::<String>::new()), or a later push fixes it.
16. Creating values of type T
Because generic code is specialized for each type, it can call associated functions on T, such as T::default().
#[derive(Debug, Default)]
struct Stats {
hits: u64,
misses: u64,
}
fn fresh_pair<T: Default>() -> (T, T) {
(T::default(), T::default())
}
fn main() {
let (a, b): (Stats, Stats) = fresh_pair();
let (x, y) = fresh_pair::<Vec<u8>>();
println!("{:?} {:?} {:?} {:?}", a, b, x, y);
}Stats { hits: 0, misses: 0 } Stats { hits: 0, misses: 0 } [] []17. Monomorphization: one copy per type
std::any::type_name::<T>() reveals which concrete type each copy of a generic function was generated for.
fn describe<T>(_value: &T) -> &'static str {
std::any::type_name::<T>()
}
fn main() {
println!("{}", describe(&42_u8));
println!("{}", describe(&3.5));
println!("{}", describe(&"text"));
println!("{}", describe(&vec![1, 2, 3]));
println!("{}", describe(&Some('x')));
}u8
f64
&str
alloc::vec::Vec<i32>
core::option::Option<char>The compiler generated five versions of describe, one for each type. The cost of monomorphization is binary size and compile time, not runtime speed.
18. Generic types are not boxed
A Vec<i32> holds the numbers themselves, side by side, and each specialization has its own exact size.
struct Wrapper<T> {
value: T,
flag: bool,
}
fn main() {
println!("Wrapper<u8>: {} bytes", std::mem::size_of::<Wrapper<u8>>());
println!("Wrapper<u64>: {} bytes", std::mem::size_of::<Wrapper<u64>>());
println!("Wrapper<[u8; 100]>: {} bytes", std::mem::size_of::<Wrapper<[u8; 100]>>());
let v: Vec<i32> = (1..=1000).collect();
println!("1000 i32s use {} bytes of element storage", v.len() * std::mem::size_of::<i32>());
let w = Wrapper { value: 1_u8, flag: true };
println!("{} {}", w.value, w.flag);
}Wrapper<u8>: 2 bytes
Wrapper<u64>: 16 bytes
Wrapper<[u8; 100]>: 101 bytes
1000 i32s use 4000 bytes of element storage
1 true19. Generic functions with &mut T
A generic function with no bounds can still move, swap, and store values. It just can't do anything type-specific with them.
fn rotate_left<T>(a: &mut T, b: &mut T, c: &mut T) {
std::mem::swap(a, b); // a=b, b=a
std::mem::swap(b, c); // b=c, c=a
}
fn main() {
let (mut x, mut y, mut z) = (1, 2, 3);
rotate_left(&mut x, &mut y, &mut z);
println!("{x} {y} {z}");
let (mut p, mut q, mut r) = (String::from("red"), String::from("green"), String::from("blue"));
rotate_left(&mut p, &mut q, &mut r);
println!("{p} {q} {r}");
}2 3 1
green blue red20. Const generics: numbers as type parameters
A type parameter can be a constant value, such as an array length. [T; N] works for arrays of any size, which was impossible with the fixed-size arrays in Tuples & Arrays.
fn sum<const N: usize>(values: [i32; N]) -> i32 {
let mut total = 0;
for v in values {
total += v;
}
total
}
fn first_and_last<T: Copy, const N: usize>(values: [T; N]) -> Option<(T, T)> {
if N == 0 { None } else { Some((values[0], values[N - 1])) }
}
fn main() {
println!("{}", sum([1, 2, 3]));
println!("{}", sum([10, 20, 30, 40, 50]));
println!("{:?}", first_and_last(['a', 'b', 'c']));
println!("{:?}", first_and_last::<u8, 0>([]));
}6
150
Some(('a', 'c'))
None21. Const generics in structs: a fixed-size matrix
The dimensions become part of the type, so multiplying matrices with incompatible sizes is a compile error, not a runtime exception.
#[derive(Debug, Clone, Copy)]
struct Matrix<const R: usize, const C: usize> {
data: [[i64; C]; R],
}
impl<const R: usize, const C: usize> Matrix<R, C> {
fn multiply<const K: usize>(&self, other: &Matrix<C, K>) -> Matrix<R, K> {
let mut data = [[0; K]; R];
for i in 0..R {
for j in 0..K {
for k in 0..C {
data[i][j] += self.data[i][k] * other.data[k][j];
}
}
}
Matrix { data }
}
}
fn main() {
let a = Matrix { data: [[1, 2, 3], [4, 5, 6]] }; // 2x3
let b = Matrix { data: [[7, 8], [9, 10], [11, 12]] }; // 3x2
let c = a.multiply(&b); // 2x2
println!("{:?}", c.data);
// b.multiply(&b); // does not compile: 3x2 times 3x2
}[[58, 64], [139, 154]]22. A generic stack
#[derive(Debug)]
struct Stack<T> {
items: Vec<T>,
}
impl<T> Stack<T> {
fn new() -> Self {
Self { items: Vec::new() }
}
fn push(&mut self, item: T) {
self.items.push(item);
}
fn pop(&mut self) -> Option<T> {
self.items.pop()
}
fn peek(&self) -> Option<&T> {
self.items.last()
}
fn len(&self) -> usize {
self.items.len()
}
fn is_empty(&self) -> bool {
self.items.is_empty()
}
}
fn main() {
let mut history: Stack<String> = Stack::new();
history.push(String::from("/home"));
history.push(String::from("/products"));
history.push(String::from("/products/42"));
println!("current: {:?}", history.peek());
println!("back from: {:?}", history.pop());
println!("now at: {:?}, {} in history", history.peek(), history.len());
let mut numbers = Stack::new();
numbers.push(1);
numbers.push(2);
while let Some(n) = numbers.pop() {
print!("{n} ");
}
println!("empty: {}", numbers.is_empty());
}current: Some("/products/42")
back from: Some("/products/42")
now at: Some("/products"), 2 in history
2 1 empty: true23. Typed IDs with PhantomData
A UserId and an OrderId are both numbers, and mixing them up is a classic bug. A generic Id<T> makes each entity's ID a distinct type. The struct doesn't store a T, so PhantomData<T> (a zero-sized marker) tells the compiler that the type parameter is used on purpose.
use std::marker::PhantomData;
#[derive(Debug)]
struct Id<T> {
value: u64,
_entity: PhantomData<T>,
}
impl<T> Id<T> {
fn new(value: u64) -> Self {
Self { value, _entity: PhantomData }
}
}
struct User;
struct Order;
fn cancel_order(user: &Id<User>, order: &Id<Order>) {
println!("user {} cancels order {}", user.value, order.value);
}
fn main() {
let user = Id::<User>::new(7);
let order = Id::<Order>::new(9001);
cancel_order(&user, &order);
// cancel_order(&order, &user); // does not compile: expected Id<User>, found Id<Order>
println!("size of Id<User>: {} bytes", std::mem::size_of::<Id<User>>());
}user 7 cancels order 9001
size of Id<User>: 8 bytesThe marker costs nothing at runtime: Id<User> is exactly 8 bytes.
24. Mixing up typed IDs is a compile error
use std::marker::PhantomData;
struct Id<T> {
value: u64,
_entity: PhantomData<T>,
}
struct User;
struct Order;
fn load_user(id: Id<User>) -> u64 {
id.value
}
fn main() {
let order_id: Id<Order> = Id { value: 9001, _entity: PhantomData };
load_user(order_id);
}error[E0308]: mismatched types
--> src/main.rs:17:15
|
17 | load_user(order_id);
| --------- ^^^^^^^^ expected `Id<User>`, found `Id<Order>`
| |
| arguments to this function are incorrect
|
= note: expected struct `Id<User>`
found struct `Id<Order>`
note: function defined here
--> src/main.rs:11:4
|
11 | fn load_user(id: Id<User>) -> u64 {
| ^^^^^^^^^ ------------25. Realistic example: a generic API response
#[derive(Debug)]
enum ApiResponse<T> {
Ok { data: T, request_id: String },
NotFound { resource: String },
Error { status: u16, message: String },
}
impl<T> ApiResponse<T> {
fn data(self) -> Option<T> {
match self {
ApiResponse::Ok { data, .. } => Some(data),
_ => None,
}
}
fn status(&self) -> u16 {
match self {
ApiResponse::Ok { .. } => 200,
ApiResponse::NotFound { .. } => 404,
ApiResponse::Error { status, .. } => *status,
}
}
}
#[derive(Debug)]
struct User {
id: u32,
name: String,
}
fn get_user(id: u32) -> ApiResponse<User> {
match id {
1 => ApiResponse::Ok { data: User { id: 1, name: String::from("Ada") }, request_id: String::from("req-1") },
2 => ApiResponse::Error { status: 503, message: String::from("database unavailable") },
_ => ApiResponse::NotFound { resource: format!("user/{id}") },
}
}
fn get_count() -> ApiResponse<u64> {
ApiResponse::Ok { data: 1_234, request_id: String::from("req-2") }
}
fn main() {
for id in [1, 2, 3] {
let r = get_user(id);
println!("{} {:?}", r.status(), r);
}
println!("count = {:?}", get_count().data());
}200 Ok { data: User { id: 1, name: "Ada" }, request_id: "req-1" }
503 Error { status: 503, message: "database unavailable" }
404 NotFound { resource: "user/3" }
count = Some(1234)26. Realistic example: a paginated result
#[derive(Debug)]
struct Page<T> {
items: Vec<T>,
page: usize,
total_items: usize,
page_size: usize,
}
impl<T> Page<T> {
fn total_pages(&self) -> usize {
self.total_items.div_ceil(self.page_size)
}
fn has_next(&self) -> bool {
self.page + 1 < self.total_pages()
}
}
fn paginate<T: Clone>(all: &[T], page: usize, page_size: usize) -> Page<T> {
let start = (page * page_size).min(all.len());
let end = (start + page_size).min(all.len());
Page { items: all[start..end].to_vec(), page, total_items: all.len(), page_size }
}
fn main() {
let products = ["mouse", "keyboard", "monitor", "webcam", "headset"];
let mut p = 0;
loop {
let page = paginate(&products, p, 2);
println!("page {}/{}: {:?}", page.page + 1, page.total_pages(), page.items);
if !page.has_next() {
break;
}
p += 1;
}
let ids: Vec<u32> = (1..=3).collect();
println!("{:?}", paginate(&ids, 0, 10));
}page 1/3: ["mouse", "keyboard"]
page 2/3: ["monitor", "webcam"]
page 3/3: ["headset"]
Page { items: [1, 2, 3], page: 0, total_items: 3, page_size: 10 }paginate requires T: Clone because to_vec() copies the page's items into the new Vec.
27. Realistic example: a small generic cache
A least-recently-inserted cache that works for any key and value types. The bounds say exactly what it needs: keys must be comparable with ==, and values must be cloneable so get can return a copy.
struct Cache<K, V> {
entries: Vec<(K, V)>,
capacity: usize,
hits: u32,
misses: u32,
}
impl<K: PartialEq, V: Clone> Cache<K, V> {
fn new(capacity: usize) -> Self {
Self { entries: Vec::new(), capacity, hits: 0, misses: 0 }
}
fn get(&mut self, key: &K) -> Option<V> {
match self.entries.iter().find(|(k, _)| k == key) {
Some((_, v)) => {
self.hits += 1;
Some(v.clone())
}
None => {
self.misses += 1;
None
}
}
}
fn put(&mut self, key: K, value: V) {
self.entries.retain(|(k, _)| *k != key);
if self.entries.len() == self.capacity {
self.entries.remove(0); // evict the oldest
}
self.entries.push((key, value));
}
}
fn expensive_lookup(id: u32) -> String {
format!("profile-{id}")
}
fn main() {
let mut cache: Cache<u32, String> = Cache::new(2);
for id in [1, 2, 1, 3, 2, 1] {
let profile = match cache.get(&id) {
Some(p) => p,
None => {
let p = expensive_lookup(id);
cache.put(id, p.clone());
p
}
};
println!("{id}: {profile}");
}
println!("hits={} misses={}", cache.hits, cache.misses);
}1: profile-1
2: profile-2
1: profile-1
3: profile-3
2: profile-2
1: profile-1
hits=2 misses=428. Realistic example: a generic min-max finder
One function works for numbers, strings, dates, or any ordered type, and returns references so nothing is copied.
fn min_max<T: PartialOrd>(items: &[T]) -> Option<(&T, &T)> {
let first = items.first()?;
let mut min = first;
let mut max = first;
for item in &items[1..] {
if item < min {
min = item;
}
if item > max {
max = item;
}
}
Some((min, max))
}
#[derive(Debug, PartialEq, PartialOrd)]
struct Date {
year: u16,
month: u8,
day: u8,
}
fn main() {
println!("{:?}", min_max(&[3.5, -1.0, 8.25, 0.0]));
println!("{:?}", min_max(&["kiwi", "apple", "mango"]));
let dates = [
Date { year: 2026, month: 3, day: 14 },
Date { year: 2025, month: 12, day: 31 },
Date { year: 2026, month: 9, day: 27 },
];
if let Some((earliest, latest)) = min_max(&dates) {
println!("{:?} .. {:?}", earliest, latest);
}
let empty: [u8; 0] = [];
println!("{:?}", min_max(&empty));
}Some((-1.0, 8.25))
Some(("apple", "mango"))
Date { year: 2025, month: 12, day: 31 } .. Date { year: 2026, month: 9, day: 27 }
NoneDate derives PartialOrd, which compares year, then month, then day, so it works with min_max without any extra code.
Summary
fn name<T>(...),struct Name<T>,enum Name<T>, andimpl<T> Name<T>declare type parameters.- Bounds (
T: PartialOrd + Copy) state what a type must support. Generic code is checked once against its bounds. impl Point<f64>adds methods only for one concrete type. Methods can have their own type parameters.- The turbofish
::<T>names a type when inference can't. - Rust monomorphizes: each concrete type gets its own compiled copy. There is no boxing and no erasure, so
T::default()works andVec<i32>stores plain integers. - Const generics (
const N: usize) put numbers such as array lengths into types. PhantomData<T>marks a type parameter that is used only at the type level, as in typed IDs.
Next: Trait Bounds & where Clauses.
