0

I am trying to pass a closure which captures a local variable:

fn main() {

  /* snip */

  let COINT = some_function_call();

  /* snip */

  hermes.with_task(
    u32::MAX,
    1,
    1,
    Box::new(
      |hermes| {
        let mut rng = rand::thread_rng();
        Command::SpawnCommand(
          COIN, 
          Vec3::new(
            rng.gen_range(-WORLD_SIZE..WORLD_SIZE),
            5.5,
            rng.gen_range(-WORLD_SIZE..WORLD_SIZE)
          )
        )
      }
    )
  );

  /* snip */

}

It is the variable COIN.

The error I receive is as follows: 
error[E0597]: `COIN` does not live long enough
   --> src\main.rs:250:11
    |
246 | /     Box::new(
247 | |       |hermes| {
    | |       -------- value captured here
248 | |         let mut rng = rand::thread_rng();
249 | |         Command::SpawnCommand(
250 | |           COIN,
    | |           ^^^^ borrowed value does not live long enough
...   |
257 | |       }
258 | |     )
    | |_____- cast requires that `COIN` is borrowed for `'static`
...
272 |   }
    |   - `COIN` dropped here while still borrowed

The method with_task is defined as follows:

impl Hermes {

  /* snip */
  
  pub fn with_task<'a: 'static>(&mut self, repeat: u32, span: u32, offset: u32, f: Box<dyn Fn(&Hermes) -> Command + 'a>) -> usize {

    let t = TaskInfo {
      task: f,
      repeat,
      span,
      counter: offset,
    };

    self.tasks.push(t);

    self.tasks.len() - 1
  }

  /* snip */

}

What puzzles me most is this line in the error:

272 |   }
    |   - `COIN` dropped here while still borrowed

This is precisely the place where the main function ends, I just do not understand what the problem is.

Thanks for helping me :)

Edit Here is a reproducible example:

struct Foo {
    tasks: Vec<Box<dyn Fn(&Foo) -> Command>>,
}
impl Foo {
    fn with_task(&mut self, f: Box<dyn Fn(&Foo) -> Command>) {
        self.tasks.push(f);
    }
}

enum Command {
    Com1(usize),
}


fn main() {

    let VAR: usize = 0;
    let mut foo = Foo {
        tasks: Vec::new(),
    };

    foo.with_task(Box::new(
        |foo| {
            Command::Com1(VAR)
        }
    ));

}
Plegeus
  • 139
  • 11
  • 3
    Spawned threads, for example, may outlive `main`. – Ivan C Jun 25 '23 at 19:20
  • 1
    @IvanC Is that true? [The docs](https://doc.rust-lang.org/std/thread/#spawning-a-thread) say, "the spawned thread may or may not outlive the spawning thread, unless the spawning thread is the main thread", which is kind of ambiguous but I would infer to mean "could outlive the spawning thread _unless_ it's the main thread". It doesn't matter for the question, though. Unless the item is declared `static` or falls into a very small number of exceptions, it doesn't matter if it gets dropped at the start or end of main, it won't be static. – isaactfa Jun 25 '23 at 19:26
  • I don't even think it's a "small number", I believe it's two: static promotion and Box::leak. – Ivan C Jun 25 '23 at 19:38
  • it's a single threaded program – Plegeus Jun 25 '23 at 19:41
  • any clues on how to solve the issue? – Plegeus Jun 25 '23 at 19:41
  • Well the definition for `with_task` says that the closure should live for at least `'static` – Ivan C Jun 25 '23 at 20:01
  • I don't see where you are getting at – Plegeus Jun 25 '23 at 20:15
  • `'a: 'static... dyn Trait +' a` is equivalent to `dyn Trait + 'static` – Ivan C Jun 25 '23 at 20:18
  • 3
    How about `Box::new(move |hermes| {` instead of `Box::new(|hermes| {`? My intuition says that the problem is that you don't move `COINT` into the task, so it stays a reference, and you cannot reference out of threads. (unless you use `std::thread::scope`, but that's not part of the given library API) – Finomnis Jun 25 '23 at 20:18
  • Also, please provide a full [MRE] in cases like this. We cannot reproduce the code as it is shown currently. There's missing parts and typos. Make sure your code, copied exactly, will produce the exact error you claim it does, otherwise it is very hard to help you properly. – Finomnis Jun 25 '23 at 20:22
  • First of all, thank you all. I added a small example, the problem to solve is actually quite is, I just want to pass a closure that captures its environment, but each time I try something like this, I run into new issues, I thought the Box solved this problem, but it doesn't. Note that I left the lifetime parameter out since I thought that would solve the problem, which it does not. – Plegeus Jun 25 '23 at 20:51
  • I don't quite understand why threading would be an issue, the application is single threaded. I also don't really understand the concept of "move", I have stumbled across it some times, but never really got it. – Plegeus Jun 25 '23 at 20:52
  • Rust doesn't know anything about threading, and it isn't an issue here. I brought that as an example of a thing that can outlive main. – Ivan C Jun 25 '23 at 20:53
  • 1
    "I just want to pass a closure that captures its environment..." You either capture the environment by `move`-ing it into the closure, or if you want to capture by reference you must add a lifetime argument to your `Foo` type: [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=bd98b2714d84c1dd3a65c1ccc2c4f0d8). Or you use `Rc` to share the environment, of course. – rodrigo Jun 26 '23 at 07:49
  • 2
    The actual issue in the minimal example is that `Box Command>` [implicitly means `Box Command + 'static>`](https://doc.rust-lang.org/reference/lifetime-elision.html#default-trait-object-lifetimes). The code works just fine after [introducing an explicit lifetime bound](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1246092fecf650d8055ef65d8d97c749), even without `move`. – Sven Marnach Jun 26 '23 at 08:40
  • Allright, it is starting to make sense now, so will move actually transfer ownership to the closure? I would like to still have access to VAR in the main method after foo.with_task was called. Secondly, I don't quite understand the need for Box, mainly because I don't really understand what Box is used for and what it does in general. – Plegeus Jun 26 '23 at 08:52

0 Answers0