11.5 Advanced Generics

11.5.1 Associated Types in Traits

Associated types in traits allow you to simplify trait definitions and implementations by associating a type with a trait.

Example:

#![allow(unused)]
fn main() {
trait Container {
    type Item;
    fn contains(&self, item: &Self::Item) -> bool;
}
}

Implementing the trait:

#![allow(unused)]
fn main() {
struct NumberContainer {
    numbers: Vec<i32>,
}

impl Container for NumberContainer {
    type Item = i32;

    fn contains(&self, item: &i32) -> bool {
        self.numbers.contains(item)
    }
}
}

11.5.2 Const Generics

As of Rust 1.51, const generics allow you to specify constant values (such as array sizes) as generic parameters.

Example:

struct ArrayWrapper<T, const N: usize> {
    elements: [T; N],
}

fn main() {
    let array = ArrayWrapper { elements: [0; 5] };
    println!("Array length: {}", array.elements.len());
}
  • Here, N is a constant generic parameter representing the size of the array.

11.5.3 Generics and Performance

Rust's generics are monomorphized at compile time, meaning that the compiler generates specialized versions of functions and structs for each concrete type used. This provides zero-cost abstractions without runtime overhead.

Monomorphization, as previously mentioned, is the process by which generic code is converted into specific code by the compiler for each concrete type that is used. This results in code that is just as efficient as if you had written it specifically for each type.

Potential for Code Bloat:

  • Code Bloat: Excessive use of generics with many different types can lead to larger binary sizes because each type results in a new instantiation of the generic code.
  • Balance: It's important to balance the flexibility of generics with the potential impact on binary size.