Advanced Generics and Type-Level Programming

Higher-Ranked Trait Bounds

In generic and type-level programming, Higher-Ranked Trait Bounds (HRTBs, for<'a>) combine with Generic Associated Types (GATs), type-level adapters, and trait projections to express constraints over lifetime-parameterized associated types for all possible lifetimes 'a.

Combining HRTBs with Generic Associated Types (for<'a> I::Item<'a>: Trait)

When writing a generic algorithm over a trait with a lifetime GAT (such as LendingIterator), you often need to require that the yielded associated type I::Item<'a> implements Debug, Display, or another trait for every borrow lifetime 'a produced inside the loop.

Writing where for<'a> I: LendingIterator, for<'a> <I as LendingIterator>::Item<'a>: Debug (or where I: LendingIterator, for<'a> I::Item<'a>: Debug) universally quantifies the bound across all loop iterations:

use std::fmt::Debug;

trait LendingSource {
    type View<'a>: Debug
    where
        Self: 'a;

    fn next_view(&mut self) -> Option<Self::View<'_>>;
}

struct LineBuffer {
    lines: Vec<String>,
    index: usize,
}

impl LendingSource for LineBuffer {
    type View<'a>
        = &'a str
    where
        Self: 'a;

    fn next_view(&mut self) -> Option<Self::View<'_>> {
        let line = self.lines.get(self.index)?;
        self.index += 1;
        Some(line.as_str())
    }
}

// Generic consumer constraining `S::View<'a>` across all lifetimes `'a`:
fn dump_all<S>(source: &mut S)
where
    S: LendingSource,
    for<'a> S::View<'a>: Debug,
{
    while let Some(view) = source.next_view() {
        println!("view: {:?}", view);
    }
}

fn main() {
    let mut buf = LineBuffer {
        lines: vec![String::from("header"), String::from("payload")],
        index: 0,
    };
    dump_all(&mut buf);
}
view: "header"
view: "payload"

Higher-Ranked Trait Objects (dyn for<'a> Trait<'a>)

A trait object type can itself be higher-ranked over a lifetime parameter: Box<dyn for<'a> Validator<'a>>. Whereas Box<dyn Validator<'static>> can only validate 'static strings, Box<dyn for<'a> Validator<'a>> can be invoked dynamically with short-lived stack strings of any lifetime 'a:

trait Validator<'a> {
    fn validate(&self, input: &'a str) -> Result<&'a str, &'static str>;
}

struct NonEmptyTrimmed;

impl<'a> Validator<'a> for NonEmptyTrimmed {
    fn validate(&self, input: &'a str) -> Result<&'a str, &'static str> {
        let trimmed = input.trim();
        if trimmed.is_empty() {
            Err("empty after trim")
        } else {
            Ok(trimmed)
        }
    }
}

fn main() {
    let rules: Vec<Box<dyn for<'a> Validator<'a>>> = vec![Box::new(NonEmptyTrimmed)];

    let local_input = String::from("   clean-token   ");
    for rule in &rules {
        println!("validated: {:?}", rule.validate(&local_input));
    }
}
validated: Ok("clean-token")

Comparing Fixed-Lifetime Bounds and Higher-Ranked Bounds in Generics

Bound Syntax Who Chooses 'a? Can 'a Be a Local Borrow Inside the Callee?
fn f<'a, T: Trait<'a>>(x: T) Caller (fixed before f starts) No ('a outlives the entire body of f)
fn f<T>(x: T) where for<'a> T: Trait<'a> Callee (f can instantiate 'a at any inner block) Yes (T implements Trait for every lifetime)
Box<dyn Trait<'a>> Bound to outer lifetime 'a No
Box<dyn for<'a> Trait<'a>> Instantiated per dynamic method call Yes