Borrowed value does not live enough for async on same scope

I'm having a lifetime problem on a large code that I cannot reproduce on a smaller example. I tried this:

use std::sync::{Arc};
use futures::future::Future;
use futures::lock::Mutex;

struct Stack<'a>{
    a: &'a u8
}

pub struct Socket<'a> {
    stack: Arc<Mutex<Stack<'a>>>,
}

pub struct LockedSmolSocket<'a> {
    stack: &'a Stack<'a>,
}

impl<'a> Socket<'a> {
    pub async fn on_lock<F, Fut>(&'a mut self, f: F)
        where
            F: Fn(&mut Self)-> Fut,
            Fut: Future<Output = ()>,
        {
            let stack_ref = self.stack.lock().await;
            let locked_smol_socket = LockedSmolSocket {
                stack: &stack_ref,
            };
           //do something with `locked_smol_socket` here
        }
}

But I don't get the error on this example.

The error is on pub async fn on_lock<F, Fut>(&'a mut self, f: F) which is exactly the same as in the large code. Here's the error:

error[E0597]: `stack_ref` does not live long enough
   --> src/networking/smol_stack_impl.rs:567:20
    |
558 |     pub async fn on_lock<F, Fut>(&'a mut self, f: F)
    |                                  ------------ lifetime `'1` appears in the type of `self`
...
563 |         let stack_ref = self.stack.lock().await;
    |                         ----------------------- argument requires that `stack_ref` is borrowed for `'1`
...
567 |             stack: &stack_ref,
    |                    ^^^^^^^^^^ borrowed value does not live long enough
...
570 |     }
    |     - `stack_ref` dropped here while still borrowed

Does anyone know any possible way why it fails in my code but not in this minimum verifiable example?

I don't see why this error would happen as I'm creating and destructing the locked_smol_socket in the same place where stack_ref's pointed instance lives.

Can you think of a situation where this error would appear? Unfortunately the original code is too large to post here.

This is necessarily an untested response, but have you tried:

- pub async fn on_lock<F, Fut>(&'a mut self, f: F)
+ pub async fn on_lock<F, Fut>(&mut self, f: F)

impl<'a> Struct<'a> { fn f(&'a mut self) {...} } is generally problematic as the exclusive borrow must last for 'a.

2 Likes

Socket<'a> means that Socket contains references to data that has been stored elsewhere, outside of that object, and that data must have been created before the Socket.

But when you reuse the same lifetime for self later, it becomes a paradox: it says that self has also been borrowed before Socket (self) has been created. That creates a maximally inflexible situation where the only solution is to make self borrowed exclusively once and forever, for its entire lifetime.

Arc<Mutex<Stack<'a>>>,

This is a very weird type. It's a non-owning temporarily borrowing Stack wrapped in owning Arc.

Stack has data that you can't own, and the data has already been borrowed for shared access. And then you're trying to own Stack via Arc, and ensure exclusive access via Mutex. These wrappers don't do anything to references. Temporary references in structs are viral, and infect and override everything they touch. You can't fully own a struct that is borrowing anything. You can't make a shared reference exclusive/mutable.

So root of your problems may be in making Stack a temporary view into some borrowed data, instead of it containing (owning) the data. Then Arc<Mutex>> would control access to Stack's data.

4 Likes

BTW, you may have difficulty with lifetimes due to:

F: Fn(&mut Self)-> Fut,

For callbacks it usually makes sense to lend them something only for duration of the call, but no longer. If you used a lifetime like 'a, then you'd allow the callback to keep the reference for way longer (theoretically store it somewhere else in a struct, and let it escape). Such requirement is usually hard to satisfy. Rust has an obscure syntax for this:

F: for<'tmp> Fn(&'tmp mut Self)-> Fut

that effectively makes a new short lifetime for every function call, and doesn't promise to be as long as anything else.

The TL,DR, is:

don't nest a borrow of a given lifetime within another borrow with the same lifetime, i.e., try not to repeat the same lifetime parameter name across a type definition.

That is, go and get rid of the &'a …<'a>, they're an antipattern, causing problems as the one you observed.

A good way to achieve that is to actually give lifetimes a more meaningful name than 'a: using 'a everywhere is a very bad practice that, alas, the Rust book gives a very bad example of.

With that, the following set of changes fixes your code:

  use std::sync::{Arc};
  use futures::future::Future;
  use futures::lock::Mutex;
  
- struct Stack<'a>{
+ struct Stack<'byte>{
-     a: &'a u8
+     a: &'byte u8
  }
  
- pub struct Socket<'a> {
+ pub struct Socket<'byte> {
-     stack: Arc<Mutex<Stack<'a>>>,
+     stack: Arc<Mutex<Stack<'byte>>>,
  }
  
- pub struct LockedSmolSocket<'a> {
+ pub struct LockedSmolSocket<'stack, 'byte> {
-     stack: &'a Stack<'a>,
+     stack: &'stack Stack<'byte>,
  }
  
- impl<'a> Socket<'a> {
+ impl<'byte> Socket<'byte> {
// Here I use an the unsugared `self` signature since it is better at showing nested borrows
-     pub async fn on_lock<F, Fut>(&'a mut self, f: F)
// equivalent to:
-     pub async fn on_lock<F, Fut>(self: &'a mut Socket<'a>, f: F)
// instead, do:
+     pub async fn on_lock<F, Fut>(self: &'_ mut Socket<'byte>, f: F)
      where
          F: Fn(&mut Self)-> Fut,
          Fut: Future<Output = ()>,
      {
          let stack_ref = self.stack.lock().await;
-         let locked_smol_socket: LockedSmolSocket<'a> = LockedSmolSocket {
// The lifetime of the `'stack` borrow is local to this function body,
// and thus unameable. Use `'_` to let it be inferred.
+         let locked_smol_socket: LockedSmolSocket<'_, 'byte> = LockedSmolSocket {
              stack: &stack_ref,
          };
         //do something with `locked_smol_socket` here
      }
  }

The more lifetime parameters you use, the less implicit lifetime equality constraints you will be featuring.

2 Likes

the fact that I use Arc<Mutex< around the Stack is that the stack (a TCP/IP stack) is shared between threads that want to send TCP data. The fact that Stack has a reference to it is because it stores an Interface<'a, DeviceT> as seen here: https://github.com/smoltcp-rs/smoltcp/blob/09f941851ded868b759e7db067abd67f2fe136a6/src/iface/interface.rs#L22

I have no option except to store this thing in my Stack so I don't see how I could have a Stack shared between threads without Arc<Mutex< and without <'a> for storing Interface<'a.

Perhaps you suggest that I do this:

pub struct Stack
{
    pub interface: Interface<'static,VirtualTunInterface>,
}

?

Since Stack is supposed to be shared between threads, then in Interface<'a>, 'a would have to be 'static?

1 Like

I see, that's a more complicated stack than the data-structures-101-stack that I've assumed. In such case there's enough other data in that object to require using a Mutex.

If you can get the Interface with a 'static lifetime, then it will make it as good as a self-contained object and it will be easy to send between threads.

In this case I think you may be lucky and smoltcp supports arbitrary lifetime, because the thing borrowed here is an empty slice, which can be a compile-time constant:

Lifetimes describe what the code does, rather than telling the code what to do. So in this case you can say it is 'static, but if it actually borrowed something short-lived, like a variable, then telling it to be 'static wouldn't be enough.

but if I borrowed something short lived, would I still be able to send Arc<Mutex<Stack<'a>>>> between threads? I thought sending to a thread required 'static

There are scoped thread abstractions, like rayon::join that don't require 'static, because they can guarantee that the function calling threaded code will be blocked for as long as needed, so that the temporarily borrowed data won't go away. In such case data with a limited lifetime is fine.

OTOH std::thread::spawn requires 'static, because it spawns a thread that may live arbitrarily long, outliving any temporary borrow. In that case you need 'static or rather, you need to get rid of a temporary & and use Arc instead. Arc is an owning alternative to &, sort of like String is an owning alternative to &str.

1 Like

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.