HRTB Lifetimes: argument requires that `storage` is borrowed for `'static`

I am having trouble with some lifetime stuff. Let me first say what I'm trying to do, hopefully that will make it clearer what the intention is.

Essentially I have a kind of storage object from which views into the storage can be constructed. Views can be constructed from other views and so on. Most of these views are pretty straightforward to implement, but there's one in particular I'm having trouble with.

This view "negates" another predicate-based view (i.e., Not<V>), but this only makes sense if V itself has another internal view (i.e. WrapperView<V> for some V, so it could look like Not<WrapperView<V>>). So I need to be able to access the sub-sub-view and thus I created a trait for doing so:

trait AsInnerRef<'a> {
    type Inner: 'a;
    
    fn as_inner(&'a self) -> Self::Inner;
}

(I return Self::Inner instead of &Self::Inner because I need to be able to return newly constructed objects that contain the references themselves in some cases).

There are certain operations that can be performed on views, for the purposes of this question I am having trouble implementing two in particular for Not

trait Contains {
    fn contains(&self) -> bool;
}

trait Remove {
    type Item;

    fn remove(&mut self) -> Self::Item;
}

I will only focus on Contains for now. I have my sub-views, these aren't really important other than to use them in the Not view below to show the error:

struct FlatView<'a>(&'a u32);

impl Contains for &FlatView<'_> {
    fn contains(&self) -> bool {
         true
    }
}

struct WrapperView<V>(V);

impl<'a, V: 'a> AsInnerRef<'a> for WrapperView<V> {
    type Inner = &'a V;
    
    fn as_inner(&'a self) -> Self::Inner {
        &self.0
    }
}

impl<V: Contains> Contains for WrapperView<V> {
    fn contains(&self) -> bool {
        self.0.contains()
    }
}

Then I have my Not view and its implementations which is where things break.

struct Not<V>(V);

impl<V> Contains for Not<V>
where
    for<'a> V: AsInnerRef<'a>,
    for<'a> <V as AsInnerRef<'a>>::Inner: Contains
{
    fn contains(&self) -> bool {
        self.0.as_inner().contains()
    }
}

(I was originally naming the lifetime explicitly which caused immediate compilation failure, someone in the Discord chat helped me to this part)

If I actually try to use this like so, it fails:

fn main() {
    let storage = 5;
    let view = Not(WrapperView(FlatView(&storage)));
    view.contains();
}
57 |     let view = Not(WrapperView(FlatView(&storage)));
   |                                         ^^^^^^^^ borrowed value does not live long enough
58 |     view.contains();
   |     --------------- argument requires that `storage` is borrowed for `'static`
59 | }
   | - `storage` dropped here while still borrowed

The closest topic I've come across similar to this is Argument requires that ... is borrowed for `'static`, but I have been unable to adopt the solution there using the 'upper_bound lifetime. I tried putting it on AsInnerRef with a corresponding 'upper_bound: 'a bound and passing an explicit 'upper_bound to the Not Contains implementation, but to no avail.

The full code for the above is here Rust Playground.

I mentioned above I was also having trouble implementing the Remove operation. The setup is identical except the trait I'm implementing: Rust Playground. Here I really don't know what lifetime I could choose for the associated type type Item = <<V as AsInnerMut<'a>>::Inner as Remove>::Item; ('a is not a lifetime in scope).

I'd appreciate any help, thanks in advance!

The bound for<'a> V: AsInnerRef<'a> requires that V implements AsInnerRef<'a> for any lifetime 'a, so in particular, it requires that V implements AsInnerRef<'static>. However, the type V is WrapperView<FlatView<'b>>, which implements AsInnerRef<'static> only if FlatView<'b>: 'static due to the V: 'a on line 27, which in turn requires 'b = 'static.

I don't know how to fix the issue.

4 Likes

Chaning Contains to:

trait Contains<'c> {
    fn contains(&'c self) -> bool;
}

(and having impl… Contains<'_> for most of the impl Contains out there), allows the problematic bound to be rewritten as:

impl<'c, V> Contains<'c> for Not<V>
where
    V : AsInnerRef<'c>, // <- no HRTB!
    <V as AsInnerRef<'c>>::Inner : for<'a> Contains<'a>,
  • Demo

  • (truth be told, I don't think this solution scales well to nested constructions, since a HRTB does end up making an appearance)

Otherwise the fix regarding the higher-order 'a in AsInnerRef is to have it carry an implicit Self : 'a bound, which is achieved, for instance, by infecting the trait with a &'a Self parameter (defaulted so that users of the trait don't see this implementation detail):

- trait AsInnerRef<'a> {
+ trait AsInnerRef<'a, WhereSelfOutlivesA = &'a Self> {
      type Inner: 'a;
    
      fn as_inner(&'a self) -> Self::Inner;
  }

Thanks to the now present implicit Self : 'a property, we can amend the impl to keep it "unbounded" (non-explicitly-bounded to be exact):

- impl<'a, V : 'a> AsInnerRef<'a> for WrapperView<V> {
+ impl<'a, V     > AsInnerRef<'a> for WrapperView<V> {
      type Inner = &'a V;
    
      fn as_inner(&'a self) -> Self::Inner {
          &self.0
      }
  }

And voilà.


Finally, you can let something like:

take care of this technicality, and, instead, write:

#[gat]
trait AsInnerRef {
    type Inner<'a>
    where
        Self : 'a,
    ;

    fn as_inner(&self) -> Self::Inner<'_>;
}

impl<V> Contains for Not<V>
where
    V : AsInnerRef,
    for<'a>
        Gat!(<V as AsInnerRef>::Inner<'a>) : Contains
    ,
{
4 Likes

Thank you for the help alice and Yandros! I understand now where the 'static is coming from. But I'm curious why using GAT on nightly doesn't work as opposed to the stable GAT library you used since I had tried it before prior to my post but still got the same error: Rust Playground

for<'a> has the same issues on nightly as it does on stable right now, so GAT's don't really help you escape them. I believe nougat simply incorporates the workaround @Yandros mentioned (or a similar one), as it's very commonly required to work with GATs

2 Likes

yeah, I think that's a GAT bug: for<'a> Bounded::Gat<'a> should be yielding a for<'a where Bound : 'a> kind of quantification, but instead it yields for<'a> Bound : 'a kind of bound, which thus boils down to Bound : 'static.

There are a bunch of bugs with GATs at the moment, which, in my probably-biased[1] opinion, make nougat actually useful even on nightly :smile:


  1. I'm the author of nougat ↩︎

1 Like

Thanks for the information! I've done some research on this the past few days, so for the sake of future readers who may come across this post I'll post a few links here regarding the problems with GAT:

Nikomatsakis's description of the problem
The Better Alternative to Lifetime GATs
Problem: for<'a> shouldn't really mean for ALL 'a

1 Like