Advanced Traits

Higher-Ranked Trait Bounds

In trait definitions and implementations, Higher-Ranked Trait Bounds (HRTBs, for<'a>) allow a trait itself—or a blanket implementation—to require that a type or supertrait holds across every possible lifetime 'a simultaneously.

Higher-Ranked Supertraits (trait OwnedDeserializer: for<'de> Deserialize<'de>)

Zero-copy deserialization frameworks (such as serde) distinguish between two kinds of deserializable types:

  1. Deserialize<'de>: Types that can borrow zero-copy slices (&'de str, &'de [u8]) from an input buffer living for 'de.
  2. DeserializeOwned: Types (like String, Vec<u8>, u64) that own all of their data and can therefore be deserialized from a temporary buffer of any lifetime 'de—even a buffer that is dropped immediately before the parser function returns!

Using a higher-ranked supertrait bound pub trait DeserializeOwned: for<'de> Deserialize<'de> {} paired with a blanket implementation expresses this relationship in three lines:

pub trait FromWire<'wire>: Sized {
    fn parse(raw: &'wire str) -> Option<Self>;
}

// Higher-ranked supertrait: any `FromWireOwned` type implements `FromWire<'wire>`
// for EVERY possible lifetime `'wire`:
pub trait FromWireOwned: for<'wire> FromWire<'wire> {}
impl<T> FromWireOwned for T where for<'wire> T: FromWire<'wire> {}

// 1. Zero-copy borrowed type: implements `FromWire<'wire>` ONLY for that `'wire`
//    (does NOT implement `FromWireOwned`!):
#[derive(Debug)]
struct BorrowedToken<'wire>(&'wire str);

impl<'wire> FromWire<'wire> for BorrowedToken<'wire> {
    fn parse(raw: &'wire str) -> Option<Self> {
        Some(BorrowedToken(raw.trim()))
    }
}

// 2. Owned type: implements `FromWire<'wire>` for ANY `'wire`
//    (automatically implements `FromWireOwned`!):
#[derive(Debug)]
struct OwnedToken(String);

impl<'wire> FromWire<'wire> for OwnedToken {
    fn parse(raw: &'wire str) -> Option<Self> {
        Some(OwnedToken(raw.trim().to_string()))
    }
}

// Reads into a temporary local buffer, parses `T`, and drops the buffer!
// Requires `T: FromWireOwned` because the temporary buffer does not outlive the call:
fn read_and_parse_owned<T: FromWireOwned>() -> Option<T> {
    let ephemeral_buffer = String::from("   owned-payload-123   ");
    T::parse(&ephemeral_buffer)
}

fn main() {
    let owned: OwnedToken = read_and_parse_owned().unwrap();
    println!("parsed from ephemeral buffer: {:?}", owned.0);

    let persistent = String::from("  zero-copy-view  ");
    let borrowed = BorrowedToken::parse(&persistent).unwrap();
    println!("parsed zero-copy view:        {:?}", borrowed.0);
}
parsed from ephemeral buffer: "owned-payload-123"
parsed zero-copy view:        "zero-copy-view"

Implementing a Trait for Higher-Ranked Function Pointers vs. Specific-Lifetime Pointers

In Rust's type system, for<'a> fn(&'a str) (a single function pointer polymorphic over all lifetimes) is a subtype of fn(&'static str). When implementing a trait for function pointers, impl<F: for<'a> Fn(&'a str)> Handler for F covers all lifetime-generic closures and functions.