Lifetime constraints for nested reference objects

I'm currently working with the http_types crate to provide additional functionality on HTTP headers that currently has not been implemented yet to provide parsing for HTTP Authorization header data.

The data model looks like this:

  • struct Headers, which owns a HashMap<HeaderName, HeaderValues>.
    • HeaderValues which owns a Vec<HeaderValue>
      • HeaderValue which owns a String

I'm not exactly sure if what I'm looking to do is possible from an API design perspective because of lifetimes, but I'm trying to build a trait which offers a zero-copy method for parsing the data in an Authorization header.

Here are my data-types for the structured data I will return from my trait method:

pub enum AuthKind<'a> {
    Basic,
    Unknown(&'a str),
}

pub struct Authorization<'a> {
    pub kind: Option<AuthKind<'a>>,
    pub data: &'a str,
}

Both of these references are to the underlying HeaderValue string data and are simple ranges over the header value string.

The trait that I'm trying to build looks like this:

pub trait HeadersExt<'a> {
    fn authorization(&self) -> Option<Authorization<'a>>;
}

I'll be implementing this trait for Headers like this:

impl<'a> HeadersExt<'a> for Headers {
    fn authorization(&self) -> Option<Authorization<'a>> {
        unimplemented!()
    }
}

A dummy implementation I built looks like this:

impl<'a> HeadersExt<'a> for Headers {
    fn authorization(&self) -> Option<Authorization<'a>> {
        let header_values: &'a HeaderValues = self.get("Authorization").unwrap();
        let header_value: &'a HeaderValue = header_values.last();

        Some(Authorization {
            kind: Some(AuthKind::Basic),
            data: header_value.as_str(),
        })
    }
}

Unfortunately, this does not compile, as it seems pretty clear that rustc does not know how long things should live:

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src/utils.rs:31:52
   |
31 |         let header_values: &'a HeaderValues = self.get("Authorization").unwrap();
   |                                                    ^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 30:5...
  --> src/utils.rs:30:5
   |
30 | /     fn authorization(&self) -> Option<Authorization<'a>> {
31 | |         let header_values: &'a HeaderValues = self.get("Authorization").unwrap();
32 | |         let header_value: &'a HeaderValue = header_values.last();
33 | |
...  |
37 | |         })
38 | |     }
   | |_____^
note: ...so that reference does not outlive borrowed content
  --> src/utils.rs:31:47
   |
31 |         let header_values: &'a HeaderValues = self.get("Authorization").unwrap();
   |                                               ^^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 29:6...
  --> src/utils.rs:29:6
   |
29 | impl<'a> HeadersExt<'a> for Headers {
   |      ^^
note: ...so that reference does not outlive borrowed content
  --> src/utils.rs:31:47
   |
31 |         let header_values: &'a HeaderValues = self.get("Authorization").unwrap();
   |                                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Is what I'm trying possible, and if so, how do I tell the compiler that the lifetime of Authorization is the same as the lifetime of the &str within the &HeaderValue within the &HeaderValues? I haven't done any super advanced lifetime work yet.

How is get defined? Also

Should probably be something like

pub trait HeadersExt {
    fn authorization<'a>(&'a self) -> Option<Authorization<'a>>;
}

Notice how I moved the <'a> from the trait to the function.

1 Like

@bjorn3 here are all the relevant methods/structs:

Headers:

HeaderValues:

HeaderValue:

Thanks @bjorn3 :clap:

The final code which does compile:

use http_types::headers::{HeaderValue, Headers};

pub enum AuthKind<'a> {
    Basic,
    Other(&'a str),
}

pub struct Authorization<'a> {
    pub kind: Option<AuthKind<'a>>,
    pub data: &'a str,
}

pub trait HeadersExt {
    fn authorization(&self) -> Option<Authorization>;
}

impl HeadersExt for Headers {
    fn authorization(&self) -> Option<Authorization> {
        Some(Authorization {
            kind: Some(AuthKind::Basic),
            data: self.get("Authorization").unwrap().last().as_str(),
        })
    }
}

What was interesting was it even appeared that it was possible to elide the lifetimes almost everywhere. I'll need to read some more articles on how lifetimes work in these scenarios: it's interesting to me that this super-nested value can be returned and the compiler can understand the lifetime without much hinting. rustc can apparently tell that self.get("").unwrap().last().as_str() will live at least as long as self does and so it's okay for it to return an Authorization whose lifetime is tied to that of self.

I also had assumed that if a type had a lifetime defined, you had to explicitly do it everywhere, but apparently NLL has made that much better? Go rustc!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.