for<'a> patterns and HRTBs
Higher-Ranked Trait Bounds (HRTBs) are constraints that require a trait implementation to work for all possible lifetimes, not just one specific lifetime. They use the for<'a> syntax to express "for any lifetime 'a, this trait bound must hold."
// Regular lifetime parameter - chosen once by caller
fn call_with_str<'a, F>(data: &'a str, f: F)
where
F: Fn(&'a str) -> bool // F works with THIS specific 'a
{
f(data);
}
// Higher-Ranked Trait Bound - must work for ALL lifetimes
fn call_with_any_str<F>(f: F)
where
F: for<'a> Fn(&'a str) -> bool // F works with ANY 'a
{
let short_lived = String::from("temporary");
f(&short_lived); // F must accept this short lifetime
f("static string"); // AND this 'static lifetime
}
Why they exist: Lifetimes are type parameters, and like types, they have "rank":
'static or specific named lifetimes<'a> for<'a>HRTBs are "higher-ranked" because they quantify over all possible lifetime choices.
The Rust compiler implicitly uses HRTBs in many common scenarios, but sometimes you must write them explicitly:
// IMPLICIT HRTB - Compiler infers for<'a>
fn takes_fn_ref(f: impl Fn(&str) -> usize) {
// Implicitly: for<'a> Fn(&'a str) -> usize
}
// EXPLICIT HRTB - Required in trait bounds and where clauses
fn explicit_version<F>(f: F)
where
F: for<'a> Fn(&'a str) -> usize
{
// Same meaning, but explicit
}
// ERROR: This doesn't work - wrong lifetime rank
fn broken_version<'a, F>(f: F)
where
F: Fn(&'a str) -> usize // 'a is chosen once, too restrictive
{
let s1 = String::from("hello");
f(&s1); // OK
let s2 = String::from("world");
f(&s2); // ERROR: s2 has different lifetime than 'a
}
HRTBs shine when you need functions that accept closures working with borrowed data:
use std::collections::HashMap;
/// A validation system that checks data without taking ownership
pub struct Validator<F>
where
F: for<'a> Fn(&'a str) -> bool,
{
predicate: F,
name: String,
}
impl<F> Validator<F>
where
F: for<'a> Fn(&'a str) -> bool,
{
pub fn new(name: impl Into<String>, predicate: F) -> Self {
Self {
predicate,
name: name.into(),
}
}
/// Validate data from any source with any lifetime
pub fn validate(&self, data: &str) -> bool {
(self.predicate)(data)
}
/// Validate multiple strings with different lifetimes
pub fn validate_all(&self, items: &[String]) -> Vec<bool> {
items.iter()
.map(|s| self.validate(s)) // Each &str has different lifetime
.collect()
}
/// Filter a collection based on validation
pub fn filter_valid<'a>(&self, items: &'a [String]) -> Vec<&'a str> {
items.iter()
.filter(|s| self.validate(s))
.map(|s| s.as_str())
.collect()
}
}
// Real-world usage: Configuration validation
pub struct ConfigValidator {
validators: HashMap<String, Box<dyn Fn(&str) -> bool>>,
}
impl ConfigValidator {
pub fn new() -> Self {
Self {
validators: HashMap::new(),
}
}
/// Add a validator - notice the HRTB in the trait object
pub fn add_validator<F>(&mut self, field: String, validator: F)
where
F: for<'a> Fn(&'a str) -> bool + 'static,
{
self.validators.insert(field, Box::new(validator));
}
/// Validate a configuration field
pub fn validate_field(&self, field: &str, value: &str) -> Result<(), String> {
match self.validators.get(field) {
Some(validator) => {
if validator(value) {
Ok(())
} else {
Err(format!("Validation failed for field: {}", field))
}
}
None => Err(format!("No validator for field: {}", field)),
}
}
}
// Example usage
fn main() {
// Create validators with different predicates
let email_validator = Validator::new(
"email",
|s: &str| s.contains('@') && s.contains('.'),
);
let port_validator = Validator::new(
"port",
|s: &str| s.parse::<u16>().is_ok(),
);
// Validate data from different scopes with different lifetimes
{
let temp_email = String::from("user@example.com");
assert!(email_validator.validate(&temp_email));
} // temp_email dropped
assert!(email_validator.validate("admin@test.org")); // 'static
// Validate a collection
let ports = vec![
String::from("8080"),
String::from("invalid"),
String::from("443"),
];
let valid_ports = port_validator.filter_valid(&ports);
assert_eq!(valid_ports, vec!["8080", "443"]);
// Config validation system
let mut config_validator = ConfigValidator::new();
config_validator.add_validator(
"email".to_string(),
|s| s.contains('@'),
);
config_validator.add_validator(
"port".to_string(),
|s| s.parse::<u16>().map(|p| p > 0 && p < 65536).unwrap_or(false),
);
// Validate from different sources
let user_input = String::from("8080");
assert!(config_validator.validate_field("port", &user_input).is_ok());
assert!(config_validator.validate_field("port", "80").is_ok()); // 'static
}
Why HRTB is required here:
The validator must work with &str references from:
String values (short lifetimes)'static)Without for<'a>, we'd need to specify a single lifetime 'a that covers all uses, which is impossible when validating data from different scopes.
Database connection traits are a classic HRTB use case - transactions need to work with queries of any lifetime:
use std::error::Error;
use std::fmt;
#[derive(Debug)]
pub struct DbError(String);
impl fmt::Display for DbError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Database error: {}", self.0)
}
}
impl Error for DbError {}
/// A row from a database query
#[derive(Debug, Clone)]
pub struct Row {
data: Vec<String>,
}
impl Row {
pub fn get(&self, index: usize) -> Option<&str> {
self.data.get(index).map(|s| s.as_str())
}
}
/// Transaction trait that can execute queries with borrowed SQL strings
/// HRTB required: queries can come from various sources with different lifetimes
pub trait Transaction {
/// Execute a query with any lifetime SQL string
fn execute(&mut self, sql: &str) -> Result<Vec<Row>, DbError>;
/// Execute within a callback - the callback must work for ANY lifetime
fn with_query<F, R>(&mut self, sql: &str, f: F) -> Result<R, DbError>
where
F: for<'a> FnOnce(&'a [Row]) -> R;
}
/// Comparison: WITHOUT HRTB (doesn't work)
///
/// pub trait BrokenTransaction<'sql> {
/// fn execute(&mut self, sql: &'sql str) -> Result<Vec<Row>, DbError>;
/// }
///
/// Problem: The 'sql lifetime is fixed when you create the transaction,
/// but we need to execute multiple queries with different lifetimes!
/// A mock database transaction
pub struct MockTransaction {
committed: bool,
}
impl MockTransaction {
pub fn new() -> Self {
Self { committed: false }
}
pub fn commit(&mut self) -> Result<(), DbError> {
self.committed = true;
Ok(())
}
}
impl Transaction for MockTransaction {
fn execute(&mut self, sql: &str) -> Result<Vec<Row>, DbError> {
// Mock implementation - in reality, would parse and execute SQL
if sql.contains("SELECT") {
Ok(vec![
Row { data: vec!["1".to_string(), "Alice".to_string()] },
Row { data: vec!["2".to_string(), "Bob".to_string()] },
])
} else {
Ok(vec![])
}
}
fn with_query<F, R>(&mut self, sql: &str, f: F) -> Result<R, DbError>
where
F: for<'a> FnOnce(&'a [Row]) -> R,
{
let rows = self.execute(sql)?;
Ok(f(&rows))
}
}
/// A database connection pool that provides transactions
pub struct ConnectionPool;
impl ConnectionPool {
pub fn new() -> Self {
Self
}
/// Execute work in a transaction - closure must accept ANY transaction lifetime
pub fn with_transaction<F, R>(&self, work: F) -> Result<R, Box<dyn Error>>
where
F: for<'a> FnOnce(&'a mut dyn Transaction) -> Result<R, DbError>,
{
let mut txn = MockTransaction::new();
let result = work(&mut txn)?;
txn.commit()?;
Ok(result)
}
}
// Real-world usage example
fn main() -> Result<(), Box<dyn Error>> {
let pool = ConnectionPool::new();
// Execute multiple queries with different SQL string lifetimes
let result = pool.with_transaction(|txn| {
// Query from a static string
let users = txn.execute("SELECT * FROM users")?;
println!("Found {} users", users.len());
// Query from a temporary String (different lifetime)
{
let table = String::from("orders");
let query = format!("SELECT * FROM {}", table);
let orders = txn.execute(&query)?;
println!("Found {} orders", orders.len());
} // query and table dropped here
// Query with callback processing
let count = txn.with_query("SELECT * FROM products", |rows| {
rows.iter().filter(|r| r.get(0).is_some()).count()
})?;
Ok(count)
})?;
println!("Final result: {}", result);
// Another transaction with completely different query lifetimes
pool.with_transaction(|txn| {
let query1 = String::from("SELECT id FROM users");
txn.execute(&query1)?;
let query2 = String::from("SELECT id FROM orders");
txn.execute(&query2)?;
Ok(())
})?;
Ok(())
}
Why HRTB is crucial here:
with_query accepts closures that must work with any row lifetimeWithout HRTB, you'd need to specify all query lifetimes upfront, making the API unusable.
Parser combinators are the canonical HRTB use case - parsers must handle input slices with any lifetime:
use std::error::Error;
use std::fmt;
/// Parse result: remaining input and parsed value, or error
pub type ParseResult<'a, T> = Result<(&'a [u8], T), ParseError>;
#[derive(Debug, Clone)]
pub struct ParseError {
message: String,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Parse error: {}", self.message)
}
}
impl Error for ParseError {}
/// A parser is a function that takes input and returns a result
/// CRITICAL: for<'a> is required because the parser must work with
/// input slices of ANY lifetime, not just one specific lifetime
pub trait Parser<Output> {
fn parse<'a>(&self, input: &'a [u8]) -> ParseResult<'a, Output>;
}
// Function-based parser using HRTB
impl<F, Output> Parser<Output> for F
where
F: for<'a> Fn(&'a [u8]) -> ParseResult<'a, Output>,
{
fn parse<'a>(&self, input: &'a [u8]) -> ParseResult<'a, Output> {
self(input)
}
}
/// Parse a single byte matching a predicate
pub fn byte_where<F>(predicate: F) -> impl Parser<u8>
where
F: Fn(u8) -> bool,
{
move |input: &[u8]| {
if input.is_empty() {
Err(ParseError {
message: "Unexpected end of input".to_string(),
})
} else if predicate(input[0]) {
Ok((&input[1..], input[0]))
} else {
Err(ParseError {
message: format!("Byte {} did not match predicate", input[0]),
})
}
}
}
/// Parse a specific byte
pub fn byte(expected: u8) -> impl Parser<u8> {
byte_where(move |b| b == expected)
}
/// Parse a sequence of bytes (literal string)
pub fn tag(expected: &'static [u8]) -> impl Parser<&'static [u8]> {
move |input: &[u8]| {
if input.len() < expected.len() {
return Err(ParseError {
message: format!("Expected {:?}, got end of input", expected),
});
}
if &input[..expected.len()] == expected {
Ok((&input[expected.len()..], expected))
} else {
Err(ParseError {
message: format!(
"Expected {:?}, got {:?}",
expected,
&input[..expected.len().min(input.len())]
),
})
}
}
}
/// Map parser output - demonstrates HRTB in combinator
pub fn map<P, F, A, B>(parser: P, f: F) -> impl Parser<B>
where
P: Parser<A>,
F: Fn(A) -> B,
{
move |input: &[u8]| {
let (remaining, value) = parser.parse(input)?;
Ok((remaining, f(value)))
}
}
/// Parse two items in sequence
pub fn pair<P1, P2, A, B>(parser1: P1, parser2: P2) -> impl Parser<(A, B)>
where
P1: Parser<A>,
P2: Parser<B>,
{
move |input: &[u8]| {
let (remaining, value1) = parser1.parse(input)?;
let (remaining, value2) = parser2.parse(remaining)?;
Ok((remaining, (value1, value2)))
}
}
/// Parse one of two alternatives
pub fn alt<P1, P2, A>(parser1: P1, parser2: P2) -> impl Parser<A>
where
P1: Parser<A>,
P2: Parser<A>,
{
move |input: &[u8]| parser1.parse(input).or_else(|_| parser2.parse(input))
}
/// Parse zero or more repetitions
pub fn many0<P, A>(parser: P) -> impl Parser<Vec<A>>
where
P: Parser<A>,
{
move |mut input: &[u8]| {
let mut results = Vec::new();
loop {
match parser.parse(input) {
Ok((remaining, value)) => {
results.push(value);
input = remaining;
}
Err(_) => break,
}
}
Ok((input, results))
}
}
// Real-world protocol parser: Simple HTTP request line
pub fn http_method() -> impl Parser<&'static str> {
move |input: &[u8]| {
alt(
map(tag(b"GET"), |_| "GET"),
alt(
map(tag(b"POST"), |_| "POST"),
alt(
map(tag(b"PUT"), |_| "PUT"),
map(tag(b"DELETE"), |_| "DELETE"),
),
),
)
.parse(input)
}
}
pub fn parse_until_space() -> impl Parser<Vec<u8>> {
many0(byte_where(|b| b != b' '))
}
pub fn http_request_line() -> impl Parser<(String, String, String)> {
move |input: &[u8]| {
let (input, method) = http_method().parse(input)?;
let (input, _) = byte(b' ').parse(input)?;
let (input, path) = parse_until_space().parse(input)?;
let (input, _) = byte(b' ').parse(input)?;
let (input, version) = parse_until_space().parse(input)?;
Ok((
input,
(
method.to_string(),
String::from_utf8_lossy(&path).to_string(),
String::from_utf8_lossy(&version).to_string(),
),
))
}
}
fn main() {
// Parse from a static buffer
let request1 = b"GET /index.html HTTP/1.1";
match http_request_line().parse(request1) {
Ok((remaining, (method, path, version))) => {
println!("Method: {}, Path: {}, Version: {}", method, path, version);
println!("Remaining: {:?}", remaining);
}
Err(e) => println!("Error: {}", e),
}
// Parse from a temporary buffer (different lifetime)
{
let temp_buffer = b"POST /api/users HTTP/1.1".to_vec();
match http_request_line().parse(&temp_buffer) {
Ok((_, (method, path, version))) => {
println!("Method: {}, Path: {}, Version: {}", method, path, version);
}
Err(e) => println!("Error: {}", e),
}
} // temp_buffer dropped
// Parse from another temporary buffer
let another_buffer = String::from("DELETE /api/users/123 HTTP/2.0");
match http_request_line().parse(another_buffer.as_bytes()) {
Ok((_, (method, path, version))) => {
println!("Method: {}, Path: {}, Version: {}", method, path, version);
}
Err(e) => println!("Error: {}", e),
}
}
Why HRTB is essential for parsers:
map, pair, many0 must work regardless of input lifetimeThe signature for<'a> Fn(&'a [u8]) -> ParseResult<'a, Output> is the heart of parser combinators.
Async middleware systems require HRTBs when handlers must work with borrowed request data:
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
/// HTTP Request with borrowed body
#[derive(Clone)]
pub struct Request<'a> {
pub method: &'a str,
pub path: &'a str,
pub body: &'a [u8],
}
/// HTTP Response
#[derive(Clone)]
pub struct Response {
pub status: u16,
pub body: Vec<u8>,
}
impl Response {
pub fn ok(body: Vec<u8>) -> Self {
Self { status: 200, body }
}
pub fn not_found() -> Self {
Self {
status: 404,
body: b"Not Found".to_vec(),
}
}
}
/// Middleware trait - must handle requests with ANY lifetime
/// HRTB required: middleware shouldn't care about request lifetime
pub trait Middleware: Send + Sync {
fn call<'a>(
&'a self,
req: Request<'a>,
) -> Pin<Box<dyn Future<Output = Response> + Send + 'a>>;
}
/// Function-based middleware using HRTB
impl<F, Fut> Middleware for F
where
F: Fn(Request<'_>) -> Fut + Send + Sync,
Fut: Future<Output = Response> + Send,
{
fn call<'a>(
&'a self,
req: Request<'a>,
) -> Pin<Box<dyn Future<Output = Response> + Send + 'a>> {
Box::pin(self(req))
}
}
/// Middleware stack that chains multiple middleware
pub struct MiddlewareStack {
middleware: Vec<Arc<dyn Middleware>>,
}
impl MiddlewareStack {
pub fn new() -> Self {
Self {
middleware: Vec::new(),
}
}
pub fn add<M: Middleware + 'static>(&mut self, m: M) {
self.middleware.push(Arc::new(m));
}
pub async fn handle<'a>(&self, req: Request<'a>) -> Response {
// In a real implementation, middleware would be chained
// For simplicity, we just call the first middleware
if let Some(m) = self.middleware.first() {
m.call(req).await
} else {
Response::not_found()
}
}
}
// Logging middleware
async fn logging_middleware(req: Request<'_>) -> Response {
println!("Request: {} {}", req.method, req.path);
// Simulate some async work
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
Response::ok(b"Logged".to_vec())
}
// Authentication middleware with HRTB
fn auth_middleware<F>(check_auth: F) -> impl Middleware
where
F: for<'a> Fn(&'a Request<'a>) -> bool + Send + Sync,
{
move |req: Request<'_>| async move {
if check_auth(&req) {
Response::ok(b"Authenticated".to_vec())
} else {
Response {
status: 401,
body: b"Unauthorized".to_vec(),
}
}
}
}
// Rate limiting middleware
struct RateLimiter {
max_requests: usize,
}
impl Middleware for RateLimiter {
fn call<'a>(
&'a self,
req: Request<'a>,
) -> Pin<Box<dyn Future<Output = Response> + Send + 'a>> {
Box::pin(async move {
// Check rate limit (simplified)
if req.path.contains("limited") {
Response {
status: 429,
body: b"Too Many Requests".to_vec(),
}
} else {
Response::ok(b"Within rate limit".to_vec())
}
})
}
}
#[tokio::main]
async fn main() {
let mut stack = MiddlewareStack::new();
// Add logging middleware
stack.add(logging_middleware);
// Add authentication middleware with custom checker
stack.add(auth_middleware(|req: &Request| {
// Check for auth header in path (simplified)
req.path.contains("authenticated")
}));
// Add rate limiter
stack.add(RateLimiter { max_requests: 100 });
// Handle requests with different lifetimes
{
let method = String::from("GET");
let path = String::from("/api/authenticated");
let body = vec![1, 2, 3];
let req = Request {
method: &method,
path: &path,
body: &body,
};
let resp = stack.handle(req).await;
println!("Response: {}", resp.status);
} // Temporary data dropped
// Static request data
let req2 = Request {
method: "POST",
path: "/api/limited",
body: b"data",
};
let resp2 = stack.handle(req2).await;
println!("Response: {}", resp2.status);
}
Why HRTB matters for async middleware:
async with trait methods requires careful lifetime handlingThe pattern for<'a> Fn(Request<'a>) -> Future allows middleware to accept requests with any lifetime while returning futures tied to that lifetime.
Lifetimes in Rust have a "rank" similar to type polymorphism:
// Rank 0: Concrete lifetime (monomorphic)
fn rank_0(data: &'static str) -> usize {
data.len()
}
// Rank 1: Generic lifetime parameter (polymorphic)
fn rank_1<'a>(data: &'a str) -> usize {
data.len()
}
// Rank 2: Universal quantification (higher-ranked)
fn rank_2<F>(f: F) -> usize
where
F: for<'a> Fn(&'a str) -> usize,
{
f("any lifetime") + f("another lifetime")
}
Key difference:
The Rust compiler automatically adds HRTB in certain contexts:
// Implicit HRTB - compiler infers for<'a>
fn implicit_fn(f: impl Fn(&str) -> bool) {
// f has type: for<'a> Fn(&'a str) -> bool
}
// Implicit in trait object
let f: Box<dyn Fn(&str) -> bool> = Box::new(|s| s.is_empty());
// Actually: Box<dyn for<'a> Fn(&'a str) -> bool>
// MUST be explicit in where clauses
fn explicit_where<F>(f: F)
where
F: for<'a> Fn(&'a str) -> bool, // Required!
{
// ...
}
// MUST be explicit in trait bounds with type parameters
trait MyTrait<F>
where
F: for<'a> Fn(&'a str) -> bool, // Required!
{
fn call(&self, f: &F);
}
// MUST be explicit in struct fields
struct MyStruct<F>
where
F: for<'a> Fn(&'a str) -> bool, // Required!
{
callback: F,
}
Understanding error messages helps identify when HRTB is needed:
// ERROR: This won't compile
fn broken_example<'a, F>(f: F)
where
F: Fn(&'a str) -> bool,
{
let s1 = String::from("hello");
f(&s1); // ERROR: 's1' lifetime is not 'a
let s2 = String::from("world");
f(&s2); // ERROR: 's2' lifetime is not 'a
}
// Compiler error:
// error[E0597]: `s1` does not live long enough
// --> src/main.rs:5:7
// |
// 4 | let s1 = String::from("hello");
// | -- binding `s1` declared here
// 5 | f(&s1);
// | ^^^ borrowed value does not live long enough
// |
// = note: the closure requires that `s1` is borrowed for `'a`
// but `s1` only lives until the end of this block
The fix: Use HRTB so F can accept any lifetime, not just 'a:
fn fixed_example<F>(f: F)
where
F: for<'a> Fn(&'a str) -> bool,
{
let s1 = String::from("hello");
f(&s1); // OK: F accepts any lifetime, including s1's
let s2 = String::from("world");
f(&s2); // OK: F accepts s2's lifetime too
}
HRTBs can quantify over multiple lifetimes:
// Function accepting closure with two independent lifetimes
fn two_lifetimes<F>(f: F)
where
F: for<'a, 'b> Fn(&'a str, &'b str) -> bool,
{
let s1 = String::from("hello");
let s2 = String::from("world");
f(&s1, &s2); // 'a and 'b are independent
f("static", &s1); // 'a = 'static, 'b = s1's lifetime
}
// Example: String comparison from different sources
fn compare_strings() {
two_lifetimes(|a: &str, b: &str| a.len() > b.len());
}
Combining HRTB with associated types requires careful syntax:
trait Parser {
type Output;
fn parse<'a>(&self, input: &'a [u8]) -> Option<(&'a [u8], Self::Output)>;
}
// Function accepting any parser with HRTB-like behavior
fn use_parser<P>(parser: &P)
where
P: Parser,
// The parse method already has HRTB-like behavior through its own 'a parameter
{
let data = vec![1, 2, 3];
parser.parse(&data);
parser.parse(&[4, 5, 6]); // Different lifetime
}
Use HRTBs when:
fn filter<F>(items: &[String], predicate: F) -> Vec<&str>
where
F: for<'a> Fn(&'a str) -> bool,
let handlers: Vec<Box<dyn for<'a> Fn(&'a Request) -> Response>>;
pub type Parser<T> = Box<dyn for<'a> Fn(&'a [u8]) -> ParseResult<'a, T>>;
trait Transaction {
fn execute(&mut self, sql: &str) -> Result<Vec<Row>>;
}
trait Middleware: for<'a> Fn(&'a Request) -> Future<Output = Response> {}
Avoid HRTBs when:
// DON'T use HRTB here
fn process_batch<'a>(items: &'a [String], processor: impl Fn(&'a str)) {
for item in items {
processor(item); // All items have same 'a
}
}
// Simpler: take ownership
fn process_owned(items: Vec<String>, processor: impl Fn(String)) {
// No lifetimes needed
}
'static data
fn process_static(items: &[&'static str]) {
// 'static is concrete, no HRTB needed
}
// DON'T over-engineer with HRTB
fn simple<'a>(s: &'a str) -> &'a str {
s // Simple lifetime parameter is fine
}
// WRONG: Missing HRTB causes lifetime errors
struct Validator<F>
where
F: Fn(&str) -> bool, // Implicit for<'a> in trait bound, but...
{
predicate: F,
}
// ERROR: This won't compile when you use it
impl<F> Validator<F>
where
F: Fn(&str) -> bool,
{
fn validate_many(&self, items: &[String]) -> bool {
// ERROR: items have different lifetime than the inferred 'a
items.iter().all(|s| (self.predicate)(s))
}
}
// FIXED: Explicit HRTB in where clause
struct ValidatorFixed<F>
where
F: for<'a> Fn(&'a str) -> bool,
{
predicate: F,
}
// WRONG: Over-complicated with HRTB
fn process_slice<F>(items: &[String], f: F)
where
F: for<'a> Fn(&'a str), // Unnecessary HRTB!
{
for item in items {
f(item);
}
}
// BETTER: Simple lifetime parameter
fn process_slice_simple<'a, F>(items: &'a [String], f: F)
where
F: Fn(&'a str), // All items have same 'a
{
for item in items {
f(item);
}
}
// EVEN BETTER: Let compiler infer
fn process_slice_inferred(items: &[String], f: impl Fn(&str)) {
for item in items {
f(item);
}
}
// WRONG: Misunderstanding the syntax
trait BadParser<'a> { // This is NOT HRTB
fn parse(&self, input: &'a [u8]) -> Option<&'a [u8]>;
}
// Problem: 'a is fixed for each implementation, can't parse
// multiple buffers with different lifetimes
// CORRECT: HRTB in trait method
trait GoodParser {
fn parse<'a>(&self, input: &'a [u8]) -> Option<&'a [u8]>;
// Each call can have different 'a
}
// Also CORRECT: HRTB in trait bound
fn use_parser<P>(parser: P)
where
P: for<'a> Fn(&'a [u8]) -> Option<&'a [u8]>,
{
// P can be called with any lifetime
}
// WRONG: Mixing concrete and higher-ranked lifetimes
fn confused<'a, F>(data: &'a str, f: F)
where
F: for<'b> Fn(&'a str, &'b str) -> bool, // Confusing!
{
// 'a is fixed but 'b is universally quantified - unusual pattern
}
// BETTER: Either make both concrete or both universal
fn better_concrete<'a, 'b, F>(data1: &'a str, data2: &'b str, f: F)
where
F: Fn(&'a str, &'b str) -> bool, // Both concrete
{
}
fn better_universal<F>(f: F)
where
F: for<'a, 'b> Fn(&'a str, &'b str) -> bool, // Both universal
{
}
// These generate identical assembly
fn without_hrtb<'a>(s: &'a str) -> usize {
s.len()
}
fn with_hrtb<F>(f: F) -> usize
where
F: for<'a> Fn(&'a str) -> usize,
{
f("test")
}
// Calling with_hrtb(|s| s.len()) produces same code as without_hrtb("test")
HRTBs can affect compilation:
impl Trait which implicitly adds HRTBHRTB doesn't affect memory layout:
use std::mem::size_of_val;
let closure = |s: &str| s.len();
// Closure size is the same regardless of HRTB
fn takes_hrtb<F: for<'a> Fn(&'a str) -> usize>(f: F) {
println!("Size: {}", size_of_val(&f));
}
fn takes_regular<'a, F: Fn(&'a str) -> usize>(f: F) {
println!("Size: {}", size_of_val(&f));
}
takes_hrtb(closure); // Same size
takes_regular(closure); // Same size
// TODO: Add correct HRTB bound
fn validate_strings<F>(validator: F) -> bool
where
F: /* YOUR BOUND HERE */
{
// Test with temporary String
let temp = String::from("test@example.com");
if !validator(&temp) {
return false;
}
// Test with static str
if !validator("admin@test.org") {
return false;
}
// Test with another temporary
let another = format!("user{}@domain.com", 123);
validator(&another)
}
fn main() {
let is_valid = validate_strings(|s: &str| {
s.contains('@') && s.len() > 5
});
println!("All valid: {}", is_valid);
}
Solution:
fn validate_strings<F>(validator: F) -> bool
where
F: for<'a> Fn(&'a str) -> bool,
{
let temp = String::from("test@example.com");
if !validator(&temp) {
return false;
}
if !validator("admin@test.org") {
return false;
}
let another = format!("user{}@domain.com", 123);
validator(&another)
}
type ParseResult<'a, T> = Option<(&'a [u8], T)>;
// TODO: Add HRTB to make this work
trait Parser<T> {
fn parse /* YOUR SIGNATURE HERE */;
}
// TODO: Implement a combinator that chains two parsers
fn then<P1, P2, A, B>(parser1: P1, parser2: P2) -> /* RETURN TYPE */
where
P1: /* YOUR BOUND */,
P2: /* YOUR BOUND */,
{
// TODO: Return a parser that runs parser1, then parser2
todo!()
}
fn main() {
// Parse a byte, then another byte
let parser = then(
|input: &[u8]| {
if input.is_empty() {
None
} else {
Some((&input[1..], input[0]))
}
},
|input: &[u8]| {
if input.is_empty() {
None
} else {
Some((&input[1..], input[0]))
}
},
);
let result = parser.parse(&[1, 2, 3]);
println!("{:?}", result);
}
Solution:
type ParseResult<'a, T> = Option<(&'a [u8], T)>;
trait Parser<T> {
fn parse<'a>(&self, input: &'a [u8]) -> ParseResult<'a, T>;
}
impl<F, T> Parser<T> for F
where
F: for<'a> Fn(&'a [u8]) -> ParseResult<'a, T>,
{
fn parse<'a>(&self, input: &'a [u8]) -> ParseResult<'a, T> {
self(input)
}
}
fn then<P1, P2, A, B>(parser1: P1, parser2: P2) -> impl Parser<(A, B)>
where
P1: Parser<A>,
P2: Parser<B>,
{
move |input: &[u8]| {
let (remaining, value1) = parser1.parse(input)?;
let (remaining, value2) = parser2.parse(remaining)?;
Some((remaining, (value1, value2)))
}
}
use std::future::Future;
use std::pin::Pin;
#[derive(Clone)]
struct Request<'a> {
path: &'a str,
headers: Vec<(&'a str, &'a str)>,
}
struct Response {
status: u16,
body: Vec<u8>,
}
// TODO: Define Middleware trait with HRTB
trait Middleware {
// YOUR SIGNATURE HERE
}
// TODO: Implement middleware chain
struct MiddlewareChain {
// YOUR FIELDS HERE
}
impl MiddlewareChain {
fn new() -> Self {
todo!()
}
fn add<M: Middleware + 'static>(&mut self, middleware: M) {
todo!()
}
async fn execute<'a>(&self, req: Request<'a>) -> Response {
todo!()
}
}
#[tokio::main]
async fn main() {
let mut chain = MiddlewareChain::new();
// Add authentication middleware
chain.add(|req: Request<'_>| async move {
if req.path.contains("admin") {
Response {
status: 401,
body: b"Unauthorized".to_vec(),
}
} else {
Response {
status: 200,
body: b"OK".to_vec(),
}
}
});
// Test with different lifetimes
let path1 = String::from("/api/users");
let req1 = Request {
path: &path1,
headers: vec![],
};
let resp1 = chain.execute(req1).await;
println!("Response 1: {}", resp1.status);
let req2 = Request {
path: "/admin/settings",
headers: vec![],
};
let resp2 = chain.execute(req2).await;
println!("Response 2: {}", resp2.status);
}
Solution:
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
#[derive(Clone)]
struct Request<'a> {
path: &'a str,
headers: Vec<(&'a str, &'a str)>,
}
struct Response {
status: u16,
body: Vec<u8>,
}
trait Middleware: Send + Sync {
fn call<'a>(
&'a self,
req: Request<'a>,
) -> Pin<Box<dyn Future<Output = Response> + Send + 'a>>;
}
impl<F, Fut> Middleware for F
where
F: Fn(Request<'_>) -> Fut + Send + Sync,
Fut: Future<Output = Response> + Send,
{
fn call<'a>(
&'a self,
req: Request<'a>,
) -> Pin<Box<dyn Future<Output = Response> + Send + 'a>> {
Box::pin(self(req))
}
}
struct MiddlewareChain {
middleware: Vec<Arc<dyn Middleware>>,
}
impl MiddlewareChain {
fn new() -> Self {
Self {
middleware: Vec::new(),
}
}
fn add<M: Middleware + 'static>(&mut self, middleware: M) {
self.middleware.push(Arc::new(middleware));
}
async fn execute<'a>(&self, req: Request<'a>) -> Response {
for m in &self.middleware {
let resp = m.call(req.clone()).await;
if resp.status != 200 {
return resp; // Short-circuit on error
}
}
Response {
status: 200,
body: b"All middleware passed".to_vec(),
}
}
}
HRTBs appear throughout the Rust ecosystem:
std::ops):
pub trait Fn<Args>: FnMut<Args> {
extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}
// Implemented as: for<'a> Fn(&'a T) when used with references
fn filter<P>(self, predicate: P) -> Filter<Self, P>
where
P: FnMut(&Self::Item) -> bool, // Implicitly: for<'a> FnMut(&'a Item)
// From nom's IResult type
pub type IResult<I, O, E = Error<I>> = Result<(I, O), Err<E>>;
// Parser trait (simplified)
pub trait Parser<I, O, E> {
fn parse(&mut self, input: I) -> IResult<I, O, E>;
}
// All nom combinators use HRTB patterns
pub fn tag<'a>(tag: &'a str) -> impl Fn(&'a str) -> IResult<&'a str, &'a str>;
// Spawning tasks with borrowed data
pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
// HRTB used internally for task scheduling
}
// Block on with HRTB for local sets
pub fn block_on<F: Future>(&self, future: F) -> F::Output;
// diesel's query execution uses HRTB
trait Connection {
fn execute(&mut self, query: &str) -> QueryResult<usize>;
fn transaction<T, E, F>(&mut self, f: F) -> Result<T, E>
where
F: FnOnce(&mut Self) -> Result<T, E>, // HRTB in practice
E: From<Error>;
}
// Middleware trait (simplified)
pub trait Transform<S, Req> {
type Response;
type Error;
type Transform: Service<Req, Response = Self::Response, Error = Self::Error>;
fn new_transform(&self, service: S) -> Self::Transform;
}
// Handler functions use HRTB for request extraction
pub trait Handler<Args>: Clone + 'static {
type Output;
fn call(&self, args: Args) -> Self::Output;
}
---
Next Steps:for<'a> in where clauses (compiler won't always infer)Run this code in the official Rust Playground