Using static lifetimes triggers E0373 when self is borrowed inside a closure


#1

I’m attempting to write some code for an ARM Cortex-M microcontroller and I’m running into an issue with the borrow checker. It is a perfectly valid error, but I want to know how I can avoid it.

My program is using the memory address of variables as a unique identifier of sorts. I know this is incredibly ugly, but it is a very lightweight way to get a unique identifier. This pattern is only valid if I make these variables static so their unique id (i.e. address) lives “forever” which means I have several functions which require a reference with a 'static lifetime.

I’m using a library by japaric (cortex-m) which provides a method which places the processor in a certain state that allows a function to run in an interrupt-free critical section. This is accomplished by a function which wraps the call to the function that needs to be executed in a critical section with the appropriate assembly calls.

In this contrived example, the wrapper function is called run_in_special_state. I need to execute the foo method in the special state. However, it requires a 'static Contrived. Here’s a playground that will illustrate the error:

fn foo(_: &'static Contrived) {
    
}

fn run_in_special_state<F, R>(f: F) -> R
where
    F: FnOnce() -> R,
{
    // Some stuff happens before the function
    let r = f();
    // Some stuff happens after the function
    r
}

struct Contrived {
    value: u32
}

impl Contrived {
    fn func (&'static mut self) {
        run_in_special_state(|| {
            foo(self)
        });
        
        self.value = 6;
    }
}

static mut INSTANCE: Contrived = Contrived { value: 4 };

fn main() {
    unsafe { INSTANCE.func() };
}

Here’s what you’ll get when you run that in the playground:

   Compiling playground v0.0.1 (file:///playground)
error[E0373]: closure may outlive the current function, but it borrows `self`, which is owned by the current function
  --> src/main.rs:21:30
   |
21 |         run_in_special_state(|| {
   |                              ^^ may outlive borrowed value `self`
22 |             foo(self)
   |                 ---- `self` is borrowed here
help: to force the closure to take ownership of `self` (and any other referenced variables), use the `move` keyword
   |
21 |         run_in_special_state(move || {
   |                              ^^^^^^^

error: aborting due to previous error

Here’s my question: I know that the FnOnce is going to be called before run_in_special_state exits. I believe this also means that the closure will not outlive the current function (func?), since it (the closure) will be executed and discarded before the current function (func) exits. How can I communicate this to the borrow checker? Or is something else going on here? I’ve noticed that if I drop the 'static requirement on foo that the error disappears.

I also can’t do the suggested fix, since I need to use self after run_in_special_state is called.


#2

Since it’s given a 'static lifetime, fn foo(_: &'static Contrived) would be within its rights to hold that reference forever, perhaps by storing it in a static variable, sending it to another thread, etc. There’s no way for you to get mutable access again after this call.


#3

Oh man you’re totally right. I didn’t realize that. And that’s why the borrow checker is complaining. I have shot myself in the foot :sweat_smile:. That’s reason enough I think to look for a different way of having unique identifiers since that’s the only reason I have 'static lifetimes.


#4

You might be able to get the address of a reference and pass that as a separate arg, keeping the borrow itself for less than 'static. So fn foo(_: &Contrived, addr: usize). Or transmute lifetimes but that’s dangerous (unsafe) for the reasons @cuviper mentioned.

But yeah, if you can avoid this 'static hack altogether it’s better all around.


#5

I know I said this was the world’s most useless library, but it sort-of looks like what you want.

  • It generates identifiers corresponding to a location in code.
  • The identifier is the same size as a u64.