Can I remove a lifetime from this trait?

See playground and code inline below.

I have one trait Format that I think needs a lifetime (I think) because it defines a type that is expected to borrow its data. I also want a second trait HasFormat that defines which format to use to store a given type. It feels redundant for HasFormat to specify a lifetime. Can anyone explain how I could redesign this so that isn't required?

Unfortunately, I can't see how to encode the method from_borrow without putting the lifetime into the Format trait, and once it's there, I can't see how to avoid putting it into the HasFormat trait, which feels very wrong because the HasFormat is implemented (typically) on primitive types that are 'static.

/// Can be stored in a particular kind of `Foo`
trait HasFormat<'a>: Sized {
    type Format: Format<'a, Self>; 
}

impl<'a> HasFormat<'a> for u8 {
    type Format = &'a u8;
}

/// Stores a reference to T in a particular format
trait Format<'a, T> : Sized {
    fn from_borrow(reference: &'a T) -> Self;
}

impl<'a, T: 'a> Format<'a, T> for &'a T {
    fn from_borrow(reference: &'a T) -> Self { reference }
}

Here playground is what happens when I try to remove the lifetime from Format. I encounter generic associated types being unstable.

/// Can be stored in a particular kind of `Foo`
trait HasFormat: Sized {
    type Format<'a>: Format<'a, Self>; 
}

impl HasFormat for u8 {
    type Format<'a> = &'a u8;
}

/// Stores a reference to T in a particular format
trait Format<'a, T> : Sized {
    fn from_borrow(reference: &'a T) -> Self;
}

impl<'a, T> Format<'a, T> for &'a T {
    fn from_borrow(reference: &'a T) -> Self { reference }
}

You can emulate GAT on stable.

2 Likes

Most likely a case of XYProblem.

What does it even mean to remove a lifetime?

You seem to be thinking about how you want to “peel out” that “borrow-checker veil” and “reach out” to the “normal”, “standard”, “typical” “tracing GC core” which is surely hideen somewhere there… forget about tracing lifetimes somehow, move it where it belongs (to said “tracing GC core”).

But Rust is not built on top of tracing GC core and lifetimes is how it tracks variables!

The fact that you are dealing with primitive types which are, indeed, 'static doesn't mean that variables if said types are 'static, too!

On the contrary: usually they are very much not 'static and, of course, when you wrap references to them and pass these references around… you have to keep these lifetimes around to ensure that safety grantees are not violated.

And the question which I would ask is not how to remove a lifetimes from this trait but why your Format trait is trying to take a reference and store it for the future use? What are you trying to achieve here and why?

Frankly, this smells like a Mistake #7 to me but without knowing more about what you are trying to achieve and why you are doing it it's hard to say for sure.

P.S. Maybe it would be good idea to just take a look on how std::fmt formatting system works? From the names it sounds if you are trying to do something similar, but there, of course, Formatter doesn't store references “for future use”, it accepts variables by reference, but then converts them to output text and uses that.

My goal is actually to build a lifetime-light API. I'm trying to hide my lifetimes internally. In this case, I have a data structure that itself holds references, and I have different ones for different primitive types. I created a trait to associate those two things, and @quinedot's solution was perfect. I really want to express that for any lifetime you want, there exists a Format for u8.

Okay.

That not how you are achieving lifetime-light API. As I have said: lifetimes are used to track, well, life times of variables used thus attempts to “hide” them would usually only lead to troubles. What would it even mean to hide them? How compiler would track life times of variables if you would hide them?

This sounds life lifetime-heavy API to me, but, as I have said, without seeing more context it's hard to say if that's good or bad decision.

If it's Format for u8 then it even deal with references and lifetimes? Why is it defined for &'a u8 and not for u8 (it may still accept &Self and return &Self)? Why it retains references to u8?

As I have said: these are questions which are hard to answer without seeing the full picture but if you are passing references with lifetimes around wrapped into different long-living data structures it's not a lifetime-light API for sure.

It maybe good API or bad API (depending on your goals) but it's definitely not a lifetime-light one.

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.