Help with error "closure may outlive the current function, but it borrows..."

Hi all,

While having a strong C/C++ and Java background I am heavily struggling to learn some of the new concepts Rust introduces. Currently, I'm pulling out my hair trying to understand lifetimes, borrowing, and such. :cry:

I'm working on a project which uses the fltk-rs crate (Rust bindings for the FLTK GUI library). I try to setup a callback function to "draw" using the method reserved for this purpose which takes a closure. Here is a small snippet of code which I think should be sufficient to understand my problem (if required, I can also post the complete code):

fn main() {
      :
   let mut grid = Grid::new(); // Grid = Hashmap<T1, T2>
   init_grid(&mut grid);       // Initializes the grid

   canvas.draw(|c| {           // FLTK method setting the "draw" method
       :
     for (p, _) in &grid {     // Iterates over the grid and draws things
       :
      }
   });

   while app.wait() {          // FLTK main event loop 
      survivors(&grid);        // Updates elements of the grid
      thread::sleep(...);      // Waits a bit 
   }
}

When building this code I get following error message(s):

rror[E0373]: closure may outlive the current function, but it borrows `grid`, which is owned by the current function
  --> src/main.rs:78:17
   |
78 |     canvas.draw(|c| {
   |                 ^^^ may outlive borrowed value `grid`
79 |         for (p, _) in &grid {
   |                        ---- `grid` is borrowed here
   |
note: function requires argument type to outlive `'static`
  --> src/main.rs:78:5
   |
78 | /     canvas.draw(|c| {
79 | |         for (p, _) in &grid {
80 | |             draw_rectf(9*p.x + c.x(), 9*p.y + c.y(), 8, 8);
81 | |         }
82 | |     });
   | |______^
help: to force the closure to take ownership of `grid` (and any other referenced variables), use the `move` keyword
   |
78 |     canvas.draw(move |c| {
   |                 ++++

However, when I use the "move" keyword as suggested I get another error (obviously because I keep using the 'grid' variable):

rror[E0382]: borrow of moved value: `grid`
  --> src/main.rs:84:19
   |
75 |     let mut grid = Grid::new();
   |         -------- move occurs because `grid` has type `HashMap<Pair, u8>`, which does not implement the `Copy` trait
...
78 |     canvas.draw(move |c| {
   |                 -------- value moved into closure here
79 |         for (p, _) in &grid {
   |                        ---- variable moved due to use in closure
...
84 |         survivors(&grid);
   |                   ^^^^^ value borrowed here after move

I've tried back and forth, tried to define 'grid' as static variable, calling a static helper function from within the closure, and things like that. To no avail. I've searched this forum and found two or three similar error messages, but they are of no real help to me.

I'd very much appreciate if someone could kick me into the right direction. As I said, if you need more code just tell me.

TIA!!

In this case, you might want to think about Rust lifetimes in pretty much the same way as the "liftime" or validity of C++ objects. The rules are very similar – basically, you aren't allowed to use a reference after its referent is gone. This includes things like iterator invalidation, which, however, is tracked statically by Rust instead of it being an informal rule. A substantial difference between the two languages is that moving in Rust is destructive by default.


As for the actual question: if the argument of draw requires a 'static callable, that means the callable isn't allowed to borrow. If moving or cloning the grid is not acceptable, then you'll have to ensure in other ways that it's not (lexically) borrowed from the point of view of the argument of canvas.draw().

This is not immediately possible statically. However, you can wrap the Grid into an Rc<RefCell<_>>, which provides reference counting and interior mutability. These in turn ensure 1. that the referent is kept alive at least as long as the closure, and 2. that you can still mutate it (dynamic borrow checking) even in the presence of Rust's prohibition of shared mutability.

2 Likes

Thanks for the quick reply!

Yes, the function is indeed declared (or defined? what would be correct to say?) as follows:

fn draw<F: FnMut(&mut Self) + 'static>(&mut self, cb: F)

This means I have to look deeper into Rc's and RefCell's? Had hoped I could've postponed these topics for a while. :wink: But heck, if I want to learn Rust I have to go this way anyway.

I'll dig into these subjects and see how far I'll get. :upside_down_face:

They should be really easy if you have come from C++ and not from “C compiled with C++ compiler”.

Box is version of C++ std::unique_ptr while Arc is std::shared_ptr. Rc is single-threaded version of std::shared_ptr, but with important twist: while in C++ an incorrect attempt to misuse such type in mutithreaded program would be a hard-to-debug bug in Rust that would a compile-time error.

That leads to the Mutex and it's really weird (for non-Rust languages) counterpart, RefCell. You can think about it as “non-thread-safe Mutex” (again, compiler would complain to you if you would try to use it in multi-thread case and thus you would know it's time to switch to Mutex).

2 Likes

Thanks! :upside_down_face:

Now, my head starts smoking. I've read the chapters about Rc and RefCell in both books "The Rust Programming Language" and "Programming Rust". However, I don't understand how this will help me. Perhaps it's just the syntax I don't get. :cry:

If someone could give a quick example how I should define a Rc<RefCell<T>> with my grid, and how I then can use it both in the closure and later on in the event loop. I'd very much appreciate your efforts. Maybe I've been sitting too long on this problem today... :cry:

TIA!!!

What happens if you just copy-paste from book let grid = Rc::new(RefCell::new(Grid::new())); and then use *grid.borrow_mut() ?

If compiler starts to complain about Sync then it's time to pull big guns and switch to let grid = Arc::new(Mutex::new(Grid::new())); and *grid.lock().unwrap();

You just have to remember that even with Rc or Arc when you pass Rc or Arc somewhere you lose access to it, which means that you have to pass clone of reference to GTK, not original (unfortunately move doesn't give you a means to do that, because move is not copy, you need to clone before calling canvas.draw).

1 Like

For testing purposes I've now simplified my code for the time being by throwing out all unncessary bits and pieces:

// let mut grid = Grid::new();
let grid = Rc::new(RefCell::new(Grid::new()));     // as proposed
canvas.draw(move |c| {
    // for (p, _) in &grid {
    for (p, _) in *grid.borrow_mut() {             // as proposed
       draw_rectf(9*p.x + c.x(), .....); 
    }
}); 
return;

And get following error:

error[E0507]: cannot move out of dereference of `RefMut<'_, HashMap<Pair, u8>>`
   --> src/main.rs:91:23
    |
91  |         for (p, _) in *grid.borrow_mut() {
    |                       ^^^^^^^^^^^^^^^^^^
    |                       |
    |                       value moved due to this implicit call to `.into_iter()`
    |                       move occurs because value has type `HashMap<Pair, u8>`, which does not implement the `Copy` trait
    |
note: this function takes ownership of the receiver `self`, which moves value
   --> /opt/rust-bin-1.65.0/lib/rustlib/src/rust/library/core/src/iter/traits/collect.rs:261:18
    |
261 |     fn into_iter(self) -> Self::IntoIter;
    |                  ^^^^
help: consider iterating over a slice of the `HashMap<Pair, u8>`'s content to avoid moving into the `for` loop
    |
91  |         for (p, _) in &*grid.borrow_mut() {

I very much like that Rust is so verbose if it comes to error messages (compare that with C++!!), however, what the heck does he now want from me? I get the feeling that I'm far, far away from having understood this language although I thought that I made some progress in the beginning. :cry:

The same program in C (using the C bindings for FLTK) is just a breeze for me. It seems that all the safety guarantees and compiler checks to avoid typical programming errors which Rust makes do come for a price.

I very much appreciate your help, however, I don't want you to spend too much time with me. I think I call it a day now and restart afresh tomorrow...

The problem is, unsurprisingly, exactly what the compiler says. You are dereferencing the mutable reference, i.e., you are trying to iterate over the value, which would consume (move) it. You can't do that – if you were allowed to move out of a reference, then it would point to an invalid/uninitialized value, i.e., it would become dangling.

Also, you should read the compiler error message till the end. It comes with the suggestion of writing

for (p, _) in &*grid.borrow_mut() {
    ...
}

or even

for (p, _) in &mut *grid.borrow_mut() {
    ...
}

(if you need to mutate the values), which should indeed work.

1 Like

Well, yes, thanks, I'll try this tomorrow and give a feedback.

OTH, TBH, a syntax like this:

&*grid.borrow_mut()       // or
&mut *grid.borrow_mut()

doesn't look like a real improvement over pointer-oriented languages like C or C++, right? I'm not complaining, but only observing. I hoped to get over such bogus syntax when using Rust.

I thought C was terrible when it comes down to this level, but this is in no way better. :wink:

But again, I'm not complaining just making some remarks while coming from languages of which Rust claims to be superior. Perhaps it's just a matter of getting familiar with the syntax.

But I would never ever (at least at this stage of my learning curve) have come up with the syntax you and the compiler proposed. Therefore: Many thanks to you, your help is appreciated!!!! :+1: :+1:

And I don't give up!! :upside_down_face:

It's more-or-less on purpose. Rust is often called “Ocaml in a C++ tench coat” and that's not too far from truth: while Rust roots go back to bunch of highly-academic languages (like Erlang, Haskell or ML) it's syntax is definitely ugly-yet-familiar curly braces language one, similar to C++.

The ploy worked: people learn Rust because they feel it's languages case in the same mold as C/C++, C#, Java or JavaScript… and after they become hooked they usually don't bother to dig and find out how came “similar” language ended up so dissimilar in it's heart (and much nicer to boot).

No such hope, I'm afraid. They tried to avoid most problematic warts (spiral rule is not used by Rust) but still wanted for it to look similar to C/C++.

TBH, I'm not sure what kind of improvement you were looking for, or what you think is wrong with the quoted snipped in the first place.

It's most definitely not "bogus".

It's all compositional. You are taking a reference to the de-referenced smart pointer returned by borrow_mut(). Dereferencing works not only on plain references and raw pointers, but also on types implementing the Deref trait, which RefMut (the return type of borrow_mut()) does.

1 Like

But that is very surprising if you recall that C++ solves the same problem in the exact same way:

void process(auto* element) {
    std::cout << *element << std::endl;
}

int main() {
    std::set s = {'A', 'B', 'C'};
    for (auto it = begin(s); it != end(s); ++it) {
        process(&*it); // <-- smart pointer to normal one
    }
}

Here we have the exact same problem as in your Rust program. And it's solved in the exact same way.

Kinda not surprising if you recall the goal (to look superficially similar to C/C++).

Thanks for your opinion! As I said: No complain at all, just observation. Every language has its pros and cons. And I am aware that currently I obviously "only" struggle with the syntax as I think that I have very well grasped Rust conceptually. At least I hope that I grasped it. :wink:

BTW: I simply LOVE the spiral rule of C b/c it is sooo consistent, I never saw this again in any other programming language. Once I learned and understood it (somewhere in the early 1980's) I was never really struggling with type definitions. You just recall this simple rule, and you are done.

But why, then, did they want Rust to look like C/C++ if it has so little in common with these?

To make people love it, of course. You can not love something that you don't use.

The very first presentation of Rust boldly proclaim what it, actually, is: Technology from the past come to save the future from itself.

It haven't actually worked 100% (ownership and borrow system become a research topic) but most things that Rust have (and which excite people with C/C++/C#/Java/Python/etc background so much) are, actually, “old news” for the research communities.

But “normal programmers” never saw them because they looked on these, saw that there are no for loop and no braces… and that was it.

Rust managed to bring “new” ideas by using disguise. It worked. Most of these “new” ideas are, actually 20-30 years old, but compared to the “industry standard” of using 40-50 years old ideas… they are new. And people are using them because they are presented in the language which looks familiar. At least superficially.

I'm not looking for any improvement I just stated that the syntax looks bogus to me. For sure, it doesn't look bogus to YOU. :wink:

For an expert like you this may come as a surprise but it's exactly THESE little details which make Rust so difficult to learn (and yes, it just comes down to syntax sometimes!).

As I mentioned several times: I DON'T COMPLAIN. I JUST OBSERVE. I don't make any recommendations b/c I can't do on this level.

And if you look at my profile: I am just a hobby programmer. But who studied computer science in the early 1980's and is perfect in C, Pascal and Assembler, and learned C++ as well as Java along the way (but never really worked as a programmer but rather in the fields of quality control and project management), and all what I am saying is: Rust is NOT superior to C if it comes to syntax. The same pitfalls, but other consequences: Rust detects them during compile time. But understanding the syntax is the same amount of work. Even Assembler's syntax is easier to learn, but o/c not easier to master. And this is the only thing I am saying:

I HOPED that Rust - besides of giving me these wonderful promises of being safe with respect to memory management, thread handling, and such - had ALSO a better syntax which is more intuitive. Using constructs like "&*" (like C++) is NOT intuitive. That's the only thing I am saying.

Again: No complaints, no ranting, just trying to UNDERSTAND a new language.

Peace. :wink:

Thanks for your inspiring thoughts! I'd agree that many ideas implemented by Rust are not new; however, what seems new to me is that Rust took the chance and pulled tight all those ideas together under one "umbrella" and thus lifting it to a new dimension. O/c I do see all the advantages and what they could bring to the world of software development, however, just presenting the best concepts of all may fail if you can't get the majority of programmers to learn the languages with which to accomplish the goals. Being similar to existing languages is not enough if the paradigms are just too much diverging. You better invent a new language at the same time.

And this is the problem. It looks the same but it isn't. Not at all.

But we are drifting away into philosophical domains; this was never my intention, sorry for that. :wink:

I'm struggling with a real problem, don't want to rant, and appreciate your help. Thank you. :+1:

I've tried recreating a running minimal repro of your program:

use fltk::{enums::*, prelude::*, *};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::thread;

type Grid = HashMap<i32, String>;

fn init_grid(grid: &mut Grid) {}

fn survivors(grid: &Grid) {}

fn main() {
    let mut grid = Rc::new(RefCell::new(Grid::new())); // Grid = Hashmap<T1, T2>
    init_grid(&mut grid.borrow_mut()); // Initializes the grid

    let app = app::App::default();
    let mut w = window::Window::default().with_size(800, 600);
    let mut canvas = frame::Frame::default_fill();
    w.end();
    w.show();

    canvas.draw({
        let grid = grid.clone();
        move |c| {
            // FLTK method setting the "draw" method
            for (p, _) in &*grid.borrow() { // Iterates over the grid and draws things
            }
        }
    });

    while app.wait() {
        // FLTK main event loop
        survivors(&grid.borrow()); // Updates elements of the grid
        thread::sleep(std::time::Duration::from_millis(16)); // Waits a bit
    }
}
2 Likes

Wow!! Thanks a lot!! You are the maintainer of cfltk, right? GREAT stuff, thnx!

Pls give me some rest tonight, but I will answer tomorrow!

Yours,
:upside_down_face:

1 Like

Works!! No compiler errors anymore. Just a warning that the mut in let mut grid = Rc::new(RefCell::new(Grid::new())); isn't necessary. :wink:

I'm not sure whether I would have ever come up with this solution by myself. :wink:

Therefore: Thank you very much! I have now a code example which I can further use to examine and learn from!

Best regards to everyone having spent time on my problem.
:upside_down_face: