Cannot borrow `calc` as mutable, as it is a captured variable in a `Fn` closure

Hello

So I'm basically trying to make a calculator in OrbTk.
This is my idea on how to do it.

I have a struct Calculator with a string property called actions. This struct also has a function add_action(String). Each time a user clicks on a graphical button the character of that button is added to the action string.
So if the user presses the button five, plus-button, and button two. The content of the action string is "5+2".

When finally the user presses enter, I interpret the string and execute the math from left to right.
I'm pretty close, but I got a typical Rust-error blocking my way.

Code:

use orbtk::*;
use std::vec::Vec;
use std::cell::RefCell;
use std::rc::Rc;

widget!(MainView);

//This struct holds all the logic and math
//to calculate what the user wants
struct Calculator {
        //This string holds all the actions (ciphers, operators,...)
        //the user presses. On enter we execute all actions in this string
        //and reset it.
        actions : String,
}
impl Calculator {
    //Constructor
    fn new() -> Self {
        Calculator{
            actions: "".to_string(),
        }
    }

    /*
     * Gets executed when a button on the calculator is pressed
     * This button gets added to the action string
     * :param pressed_key: String representation of the key the user pressed
     */
    fn add_action(&mut self, pressed_key : &String) {
        self.actions.push_str(pressed_key);
    }
}

impl Template for MainView {
    fn template(self, _: Entity, ctx: &mut BuildContext) -> Self {

        //Delcare Calculator logic here
        let mut calc = Calculator::new();

        //Initializing Grid
        let mut grid = Grid::create(); 

        //Configuring grid (amount of rows and columns)
        grid = grid
            .columns(
                Columns::create()
                    .column("*")
                    .column("*")
                    .column("*")
                    .column("*")
                    .build()
            )
            .rows(
                Rows::create()
                    .row("*")
                    .row("*")
                    .row("*")
                    .row("*")
                    .build()
            );
        
        //Adding all buttons from 1-9 to the grid
        //in calculator format
        let mut counter : u8 = 9;
        for i in 0..3 {
            for j in 0..3 {
                grid = grid.child(
                    Button::create()
                        .text(counter.to_string())
                        .attach(Grid::column(2-j))
                        .attach(Grid::row(i))
                        .on_click(move |_states, _| -> bool {
                            //Add to Calculator's action-string when clicked
                            calc.add_action(&counter.to_string());
                            true
                        })
                        .build(ctx)
                );

                counter = counter - 1;
            }
        }

        self.name("MainView").child(
            grid.build(ctx)
        )
   } 
}

fn main() {
    Application::new()
        .window(|ctx| {
            Window::create()
                .title("OrbTk - Calculator")
                .position((100.0, 100.0))
                .size(420.0, 730.0)
                .child(MainView::create().build(ctx))
                .build(ctx)
        })
        .run();
}

Error:

error[E0596]: cannot borrow `calc` as mutable, as it is a captured variable in a `Fn` closure
  --> src/main.rs:74:29
   |
74 | ...                   calc.add_action(&counter.to_string());
   |                       ^^^^ cannot borrow as mutable
   |
help: consider changing this to accept closures that implement `FnMut`
  --> src/main.rs:72:35
   |
72 |                           .on_click(move |_states, _| -> bool {
   |  ___________________________________^
73 | |                             //Add to Calculator's action-string when clicked
74 | |                             calc.add_action(&counter.to_string());
75 | |                             true
76 | |                         })
   | |_________________________^

warning: variable does not need to be mutable
  --> src/main.rs:38:13
   |
38 |         let mut calc = Calculator::new();
   |             ----^^^^
   |             |
   |             help: remove this `mut`
   |
   = note: `#[warn(unused_mut)]` on by default

error[E0382]: use of moved value: `calc`
  --> src/main.rs:72:35
   |
38 |         let mut calc = Calculator::new();
   |             -------- move occurs because `calc` has type `Calculator`, which does not implement the `Copy` trait
...
72 |                         .on_click(move |_states, _| -> bool {
   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^ value moved into closure here, in previous iteration of loop
73 |                             //Add to Calculator's action-string when clicked
74 |                             calc.add_action(&counter.to_string());
   |                             ---- use occurs due to use in closure

error: aborting due to 2 previous errors

I asked about this issue on Rust's Discord channel and @Arnavion was so kind to help me further with the following suggestions:

  • If on_click wasn't in my control I had to use Mutex or RefCell to change Fn into FnMut.
.on_click({ let calc = RefCell::new(calc); move |_states, _| -> bool { let calc = calc.borrow_mut(); calc.add_action(...); true } })
  • But because it was all in a loop: You need to share calc amongst multiple closures, so it needs to be created outside the loop, and needs to be in an Rc so that each closure owns a clone of the Rc , ie the same RefCell.

So after implementing that suggestion this way:

    let mut calc = RefCell::new(Calculator::new());

        //Initializing Grid
        let mut grid = Grid::create(); 

       
        //Adding all buttons from 1-9 to the grid
        //in calculator format
        let mut counter : u8 = 9;
        for i in 0..3 {
            for j in 0..3 {
                grid = grid.child(
                    Button::create()
                        .text(counter.to_string())
                        .attach(Grid::column(2-j))
                        .attach(Grid::row(i))
                        .on_click({let calc = RefCell::new(calc); move |_states, _| -> bool {
                            //Add to Calculator's action-string when clicked
                            let mut calc = calc.borrow_mut();
                            calc.add_action(&counter.to_string());
                            true
                        }})
                        .build(ctx)
                );

                counter = counter - 1;
            }
        }

I still got this error:

error[E0382]: use of moved value: `calc`
  --> src/main.rs:77:60
   |
37 |         let mut calc = Calculator::new();
   |             -------- move occurs because `calc` has type `Calculator`, which does not implement the `Copy` trait
...
77 |                         .on_click({let calc = RefCell::new(calc); move |_states, _| -> bool {
   |                                                            ^^^^ value moved here, in previous iteration of loop

error: aborting due to previous error

I'm pretty sure it are good suggestions, I only have no idea what it means.
I know that Rc is a special kind of reference which allows a variable to have multiple owners, while normally a variable can only have one owner, and other instances can 'borrow' it but don't own it.
And RefCell is according to Google a run-time borrow-checker. Whatever that means...

A bump in the right direction with some explanation would be appreciated! :smiley:
Thanks!

This was a working solution:

let calc = Rc::new(RefCell::new(Calculator::new()));
on_click({
    let calc = Rc::clone(&calc);
    move |_states, _| -> bool {
        //Add to Calculator's action-string when clicked
        let mut calc = calc.borrow_mut();
        calc.add_action(&counter.to_string());
        true
    }
})

However, an extra word of explanation about Clone, Rc, and RefCell would be appreciated! What exactly is happening in this code?

You are creating multiple objects (closures inside the Button) that need to have a reference to the Calculator, so you need some sort of cloneable reference to Calculator.

It can't be &'static because Calculator is not 'static (you could Box::leak it or make it a static variable, but that's bad practice in general, although it work would here); it can't be &'a for a non-static lifetime since Button and the closure type it takes have no lifetimes, and so it can only be an Rc or Arc, and an Rc apparently works fine here since the Button closure doesn't need to be Send.

You also need to call a method that requires &mut on it, so you need to put it in a RefCell or a Mutex, and again since Button doesn't need to be Send, RefCell is fine.

So it needs to be an Rc<RefCell<T>>, and each closure needs a clone of the reference-counted pointer so you must clone it outside the closure for each closure you create.

3 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.