How to end borrow in this code?

The lifetime 'b is an input parameter to your fn test, and that means that the caller determines 'b. So long as the other bounds are met, they could call test with a lifetime that's longer than the function body, e.g. they could call it with 'static. But you're trying to borrow from a local variable, so you can't actually support such a lifetime. You can only support a lifetime that, from the implementer of Storage's perspective, is arbitrarily short.

The way one usually writes a bound like that is to not accept a specific lifetime, and instead require that a trait be implemented for any lifetime; if that is true, then you are free to use any lifetime that works within your function. And this is exactly what you tried:

  fn test<S: for<'any> Storage<'any>>()
    where &'static [u8]: for<'any> super::BufferedInput<'any, <S as Storage<'any>>::Buffer>,

And as you noted, this allows your function to compile, but these bounds cause one if your test cases to not compile.

   = note: `BufferedInput<'0, ()>` would have to be implemented for the type `&'static [u8]`, for any lifetime `'0`...
   = note: ...but `BufferedInput<'1, ()>` is actually implemented for the type `&'1 [u8]`, for some specific lifetime `'1`

And, sure enough, quick-xml has only implemented BufferedInput<'static, ()> for &'static [u8]:

impl<'i> BufferedInput<'i, ()> for &'i [u8] {

And this seems reasonable, you can't return a &'static [u8] if you're a &'something_shorter [u8] after all. But we'll come back to this later.

We can connect this current state back to your original bounds error. Remember, with those bounds, the caller can choose the lifetime, so long as the other bounds are met. And the only way for this now-failing caller to have called your original fn test is by choosing the lifetime 'static for 'b, so that the BufferedInput bounds gets met. That in turn would have required borrowing your local S for a 'static lifetime, when it's merely a local variable.

You can't have the caller choosing the lifetime anyway, but maybe this makes the reason why more clear.

If instead of requiring a static buffer &[u8], you allow yourself to choose any lifetime for the buffer, everything compiles and passes tests.

    fn test<S>()
    where
        for<'b> S: Storage<'b>,
        for<'b> &'b [u8]: super::BufferedInput<'b, <S as Storage<'b>>::Buffer>,

But I'm not sure that's actually what you want.

Let's go back to this part:

impl<'i> BufferedInput<'i, ()> for &'i [u8] {
    fn read(&mut self, _buf: ()) -> &'i [u8] {
        self
    }
}

While it's true you can't borrow a 'static from something shorter, you can borrow something shorter from a 'static:

// `&'static [u8]` in particular implements
// `for<'any> BufferedInput<'any, ()>`
impl<'long: 'short, 'short> BufferedInput<'short, ()> for &'long [u8] {
    fn read(&mut self, _buf: ()) -> &'short [u8] {
        self
    }
}

And this means that &'static [u8] can meet your bounds now. I'm still not entirely sure that this is what you want -- the way your Storage trait is written, you're always limited by the lifetime of the local S -- but I think it's at least closer.

However, this is a change to the quick-xml code, not yours. But it's a very reasonable one to request.

You may wonder why we had to spell this out, and the answer is that lifetimes in trait parameters are invariant -- they can't automatically shrink, like you're used to with &[u8], say. The above change makes the implementation covariant by breaking up the lifetime of the implemented trait ('short) and the lifetime of the implementer ('long) -- every concrete &'long [u8] gets an implementation for every 'short that is shorter.

Just an aside to explain the immediate error, an implementer of Storage<'b> has to implement buffer that takes a &'b mut Self. If Self wasn't valid for at least 'b, this would require taking a reference that lasts longer than the underlying object. Rust is ok with you not requiring this at the definition of the trait, because it won't be allowed elsewhere, and maybe your trait has other uses... so it only complained when you tried to call buffer. In this case, your trait is useless without that bound, so you could just add it to the trait: trait Storage<'b>: Default + 'b. Then you don't have to mention it elsewhere.

It might be cleaner to break up Storage into two parts, one with a lifetime that defines the type, and another with no lifetime that defines the function. But I'll leave it here for now.

3 Likes