#URIs with generic backing storage: lifetime problem

29 messages · Page 1 of 1 (latest)

granite willow
#

Hi! I tried to make the following code generic. The goal is to make the output reference of a Uri live as long as possible, while reducing repetitive code.

struct Uri<T> {
    storage: T,
    // ...Parsed component bounds, etc.
}

impl<T> Uri<T> {
    fn parse(storage: T) -> Uri<T> {
        Uri { storage }
    }
}

impl<'a> Uri<&'a str> {
    fn as_str(&self) -> &'a str {
        self.storage
    }
}

impl Uri<String> {
    fn as_str(&self) -> &str {
        &self.storage
    }
}

// these are the desired behavior:
fn ref_outlives_borrowed_uri(s: &str) -> &str {
    Uri::parse(s).as_str()
}
fn ref_does_not_outlive_owned_uri() -> &'static str {
    // error: cannot return value referencing temporary value
    Uri::parse(String::new()).as_str()
}
#

But then I ran into some problem with lifetimes:

use std::{fmt, marker::PhantomData, slice, str};

trait Storage {}

impl Storage for &str {}
impl Storage for String {}

trait StorageHelper<'i, 'o>: Storage {}

impl<'i, 'o> StorageHelper<'i, 'o> for &'o str {}
impl<'a> StorageHelper<'a, 'a> for String {}

struct Uri<T: Storage> {
    ptr: *mut u8,
    len: u32,
    cap: u32,
    // ...Parsed component bounds, etc.
    _marker: PhantomData<T>,
}

impl<'i, 'o, T: StorageHelper<'i, 'o>> Uri<T> {
    fn parse(_: T) -> Uri<T> {
        todo!()
    }

    fn as_str(&'i self) -> &'o str {
        unsafe { str::from_utf8_unchecked(slice::from_raw_parts(self.ptr, self.len as _)) }
    }
}

// these are the desired behavior:
fn ref_outlives_borrowed_uri(s: &str) -> &str {
    Uri::parse(s).as_str()
}
fn ref_does_not_outlive_owned_uri() -> &'static str {
    // error: cannot return value referencing temporary value
    Uri::parse(String::new()).as_str()
}

// when i try to impl `Display` generically for `Uri<T>`,
// the following error occurs.
//
// error: lifetime may not live long enough
// argument requires that `'a` must outlive `'i`
//
// impl<'i, 'o, T: StorageHelper<'i, 'o>> fmt::Display for Uri<T> {
//     fn fmt<'a>(&'a self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
//         fmt::Display::fmt(self.as_str(), f)
//     }
// }

// i had to specialize the impls, which is sad:
impl fmt::Display for Uri<&str> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(self.as_str(), f)
    }
}
impl fmt::Display for Uri<String> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(self.as_str(), f)
    }
}

I'm trying to figure out a way to implement Display generically like I did above. I do have some idea now, but I wonder if you'd offer me some advice? Thanks!

steep harness
#

You could use Cow<'static, str> instead.

#

Or Cow<'a, str>.

granite willow
#

I can't seem to write the as_str function using a Cow<'a, str>. Am I missing something or is it actually not possible?

steep harness
#

Probably want Cow::as_ref()

granite willow
#

Do you mean something like this?

use std::borrow::Cow;

struct Uri<'a>(Cow<'a, str>);

impl<'a> Uri<'a> {
    fn as_str(&self) -> &str {
        &self.0
    }
}

But what I need in fact is to make Uri<'a>::as_str return &'a str when the uri is borrowed.

steep harness
#
impl<'a> Uri<'a> {
    fn as_str(&self) -> &'a str {
        &*self.0
    }
}
granite willow
#

it doesn't compile ferrisFrown

#

The goal is to make as_str(&'a Uri<String>) return &'a str and as_str(&'a Uri<&'b str>) return &'b str.

#

so that the following function compiles fine:

fn ref_outlives_borrowed_uri(s: &str) -> &str {
    Uri::parse(s).as_str()
}
#

I'm not quite certain about my idea, but here it is, with GATs:

use std::{fmt, marker::PhantomData, slice, str};

trait Storage {
    type Ref<'a>;
}

impl<'o> Storage for &'o str {
    type Ref<'i> = &'o ();
}
impl Storage for String {
    type Ref<'a> = &'a ();
}

trait StorageHelper<'i, 'o>: Storage {}

impl<'i, 'o, T: Storage> StorageHelper<'i, 'o> for T where T::Ref<'i>: 'o {}

struct Uri<T: Storage> {
    ptr: *mut u8,
    len: u32,
    cap: u32,
    // ...Parsed component bounds, etc.
    _marker: PhantomData<T>,
}

impl<'i, 'o, T: StorageHelper<'i, 'o>> Uri<T> {
    fn parse(_: T) -> Uri<T> {
        todo!()
    }

    fn as_str(&'i self) -> &'o str {
        unsafe { str::from_utf8_unchecked(slice::from_raw_parts(self.ptr, self.len as _)) }
    }
}

// these are the desired behavior:
fn ref_outlives_borrowed_uri(s: &str) -> &str {
    Uri::parse(s).as_str()
}
fn ref_does_not_outlive_owned_uri() -> &'static str {
    // error: cannot return value referencing temporary value
    Uri::parse(String::new()).as_str()
}

impl<T: Storage> fmt::Display for Uri<T> {
    fn fmt<'a>(&'a self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(self.as_str(), f)
    }
}
#

It looks so cursed I still haven't wrapped my head around it. Can anyone tell if it's sound or not?

steep harness
#

Yeah oops. I think you need to make as_str() a trait method, because you are really returning a different type based on T.

granite willow
#

that is indeed possible with GATs, but there comes another problem:

use core::fmt;

trait Storage {
    type Ref<'a>
    where
        Self: 'a;
    fn as_str<'a>(&'a self) -> Self::Ref<'a>;
}

impl<'o> Storage for &'o str {
    type Ref<'i> = &'o str where Self: 'i;
    fn as_str<'a>(&'a self) -> Self::Ref<'a> {
        self
    }
}

impl Storage for String {
    type Ref<'a> = &'a str where Self: 'a;
    fn as_str<'a>(&'a self) -> Self::Ref<'a> {
        self
    }
}

struct Uri<T: Storage> {
    storage: T,
}

impl<T: Storage> fmt::Display for Uri<T> {
    fn fmt<'a>(&'a self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(&self.storage.as_str(), f)
    }
}

you don't know if <T as Storage>::Ref is &str, and that makes it very hard if you want to rely on this fact

steep harness
#

?eval

trait Storage {
    type Str<'a> where Self: 'a;
    fn as_str<'a>(&'a self) -> Self::Str<'a>;
}

impl<'b> Storage for &'b str {
    type Str<'a> = &'b str where Self: 'a;
    fn as_str<'a>(&'a self) -> &'b str {
        self
    }
}

impl Storage for String {
    type Str<'a> = &'a str where Self: 'a;
    fn as_str<'a>(&'a self) -> &'a str {
        self.as_ref()
    }
}

struct Uri<T>(T);

impl<T: Storage> Uri<T> {
    fn as_str<'a>(&'a self) -> T::Str<'a> {
        self.0.as_str()
    }
}
low dragonBOT
#
()```
steep harness
#

Had no idea about the where clause with the GAT, this is wild.

granite willow
#

this also makes the API very ugly... there would be T::Strs everywhere

#

I tried it for a moment and then I gave up

steep harness
#

Probably just use string. Cow<'static, str> is nice if you want to store str literals. But copying strings is fast if they are small.

granite willow
#

I guess I'll stick to a zero-copy design ferrisThink

#

if only there were "generic associated lifetimes"

#

it would be something like:

trait Storage {
    lifetime 'o<'i>;
    fn as_str(&'i self) -> &'o<'i> str;
}

impl Storage for &'a str {
    lifetime 'o<'i> = 'a;
    fn as_str(&self) -> &'a str {
        self
    }
}

impl Storage for String {
    lifetime 'o<'i> = 'i;
    fn as_str(&self) -> &str {
        self
    }
}
low dragonBOT
#
()```
granite willow
#

let's see if this is going to work

low dragonBOT
#
error[E0276]: impl has stricter requirements than trait
 --> src/main.rs:9:65
  |
4 |     fn as_str<'i, 'o>(&'i self) -> &'o str where Self::Ref<'i>: 'o;
  |     --------------------------------------------------------------- definition of `as_str` from trait
...
9 |     fn as_str<'i, 'o>(&'i self) -> &'o str where Self::Ref<'i>: 'o {
  |                                                                 ^^ impl has extra requirement `'a: 'o`
  |
help: copy the `where` clause predicates from the trait
  |
9 |     fn as_str<'i, 'o>(&'i self) -> &'o str where <Self as Storage>::Ref<'i>: 'o {
  |                                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

For more information about this error, try `rustc --explain E0276`.```
#
error[E0276]: impl has stricter requirements than trait
 --> src/main.rs:9:65
  |
4 |     fn as_str<'i, 'o>(&'i self) -> &'o str where Self::Ref<'i>: 'o;
  |     --------------------------------------------------------------- definition of `as_str` from trait
...
9 |     fn as_str<'i, 'o>(&'i self) -> &'o str where Self::Ref<'i>: 'o {
  |                                                                 ^^ impl has extra requirement `'a: 'o`
  |
help: copy the `where` clause predicates from the trait
  |
9 |     fn as_str<'i, 'o>(&'i self) -> &'o str where <Self as Storage>::Ref<'i>: 'o {
  |                                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

For more information about this error, try `rustc --explain E0276`.```
granite willow
#

what does this even mean ferrisballSweat