Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to change the variable from inside Fn closure in Rust?

I have the following code (playground):

struct A {
    pub vec: Vec<u64>,
}

impl A {
    fn perform_for_all<F: Fn(&mut u64)>(&mut self, f: F) {
        for mut i in &mut self.vec {
            f(i);
        }
    }
}
fn main() {
    let mut a = A {
        vec: vec![1, 3, 44, 2, 4, 5, 6],
    };

    let mut done = false;

    a.perform_for_all(|v| {
        println!("value: {:?}", v);
        done = true;
    });

    if !done {
        a.perform_for_all(|v| {
            println!("value {:?}", v);
        });
    }
}

The following errors occur:

error[E0594]: cannot assign to `done`, as it is a captured variable in a `Fn` closure
  --> src/main.rs:21:9
   |
21 |         done = true;
   |         ^^^^^^^^^^^ cannot assign
   |
help: consider changing this to accept closures that implement `FnMut`
  --> src/main.rs:19:23
   |
19 |       a.perform_for_all(|v| {
   |  _______________________^
20 | |         println!("value: {:?}", v);
21 | |         done = true;
22 | |     });
   | |_____^

I have a list of loaded objects and a list of objects in a database. I need a function that takes a closure and executes it on the loaded objects and if we don't have the objects in the list, execute it on a list of objects from the database.

That function looks like:

pub fn perform_for_match_with_mark<F>(&mut self, mark: MatchMark, f: F)
where
    F: Fn(&mut GameMatch),
{
    self.perform_for_all_matches(
        |m| {
            // runtime list
            if let Game::Match(ref mut gm) = *m {
                if gm.match_stamp().mark == mark {
                    f(gm);
                }
            }
        },
        None,
    );
    // if we have called `f` above - don't execute lines below.
    let tx = self.match_tx.clone();
    GamesDatabase::perform_for_match_with_mark(mark, |ms| {
        // database
        self.perform_for_all_matches(
            |m| {
                if let Game::Match(ref gm) = *m {
                    if gm.match_stamp().id == ms.id {
                        f(&mut GameMatch::new_with_match_stamp(
                            tx.clone(),
                            ms.clone(),
                            gm.needs_server_set,
                            gm.server_id,
                        ))
                    }
                }
            },
            None,
        );
    });
}

We have to operate on objects from the database only if we were unable to find them in runtime list. That is why I decided to make a variable which says "we already found these objects in the list, leave the database alone".

like image 713
Victor Polevoy Avatar asked Dec 20 '16 09:12

Victor Polevoy


People also ask

How do you pass closure to a function in Rust?

Rust closures are anonymous functions without any name that you can save in a variable or pass as arguments to other functions. Unlike functions, closures can capture values from the scope in which they're defined. let closure_example = |num| -> i32 { num + 1 }; It is a simple example of closure.

What is a closure in Rust?

Rust's closures are anonymous functions you can save in a variable or pass as arguments to other functions. You can create the closure in one place and then call the closure elsewhere to evaluate it in a different context. Unlike functions, closures can capture values from the scope in which they're defined.

What is move keyword in Rust?

Keyword move move converts any variables captured by reference or mutable reference to variables captured by value.

Why Rust closures are somewhat hard?

Rust closures are harder for three main reasons: The first is that it is both statically and strongly typed, so we'll need to explicitly annotate these function types. Second, Lua functions are dynamically allocated ('boxed'.)


1 Answers

Change your perform_for_all function to use FnMut instead of Fn:

fn perform_for_all<F>(&mut self, mut f: F)
where
    F: FnMut(&mut u64),
{
    for mut i in &mut self.vec {
        f(&mut i);
    }
}

As Peter said, there is some compiler magic going on.

The signature for Fn::call is:

extern "rust-call" fn call(&self, args: Args) -> Self::Output

This takes an immutable reference to self, which is why you can't modify any of the captured variables.

The signature for FnMut::call_mut lets you mutate variables because it takes &mut self:

extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output

By changing your closure from Fn to FnMut, you allow it to modify its captured variables, given that the references you pass to it are mutable.

like image 81
SplittyDev Avatar answered Sep 18 '22 14:09

SplittyDev