Los Frijoles
Los Frijoles

Reputation: 4821

Using static lifetimes triggers "closure may outlive function" when self is borrowed inside a closure

My program uses the memory address of variables as a unique identifier. 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 the cortex-m crate which provides a method which places the processor in a 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 an example that illustrates 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:

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

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? Is something else going on here? I've noticed that if I drop the 'static requirement on foo that the error disappears.

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

Upvotes: 2

Views: 452

Answers (1)

red75prime
red75prime

Reputation: 3861

The signatures of those functions:

  • fn foo(_: &'static Contrived)

  • fn func (&'static mut self)

require references which borrow their values for the duration of entire program, while you need references which borrow their values just long enough.

Remove 'static and the program will compile:

fn foo(_: &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(&mut self) {
        run_in_special_state(|| foo(self));

        self.value = 6;
    }
}

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

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

Playground

&'static T isn't just an address of a variable, it bears additional semantics. If you want to use it as a unique identifier, you'll probably be better off creating a type, which retains only the uniqueness of the address and don't borrow the value:

mod key {
    use super::Contrived;

    #[derive(Debug, Hash)]
    pub struct ContrivedId(usize);

    impl ContrivedId {
        pub fn new(r: &'static Contrived) -> Self {
            ContrivedId(r as *const _ as usize)
        }
    }
}

use key::ContrivedId;

fn foo(_: ContrivedId) {}

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
}

pub struct Contrived {
    value: u32,
}

impl Contrived {
    fn func(&mut self, id: ContrivedId) {
        run_in_special_state(|| foo(id));

        self.value = 6;
    }
}

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

fn main() {
    unsafe {
        let id = ContrivedId::new(&INSTANCE);
        INSTANCE.func(id)
    };
}

Playground

Upvotes: 1

Related Questions