How to create a closure without capture variables?

fn main() {
    let mut a = 10;
    let mut func = || {
        a += 10;
        // and much more...
    };
    a += 20;
    func(); // will be used many times
}

This demo won't compile because func captures a but which I used at a += 20 later. It's very annoying when we just use the closure itself for convenience without moving it. I considered using macro_rules but I think it may cause the size of generated files to expand. I hope someone could tell me a way to translate the above code to the code similar to the following automatically via using external crates or something else:

fn main() {
    let mut a = 10;
    let func = |a: &mut i32| {
        (*a) += 10;
        // and much more...
    };
    a += 20;
    func(&mut a); // will be used many times
}

Thanks in advance.


Btw, rustc forced me to give a a type in this case, obviously a is &mut i32, can't it infer automatically?

fn this_compiles() {
    let mut a: i32 = 10;
    let func = |a| {
        (*a) += 10;
        // and much more...
    };
    a += 20;
    func(&mut a); // will be used many times
}
error[E0282]: type annotations needed
  --> src/main.rs:15:17
   |
15 |     let func = |a| {
   |                 ^ consider giving this closure parameter a type
   |
   = note: type must be known at this point

For more information about this error, try `rustc --explain E0282`.
1 Like

That's usually not a concern. Your final compiled binary will optimize out most such repetitions.

Not yet, but work is going on to improve this.

I'd say closures aren't particularly good for using like that. A simple macro will do much better.

2 Likes

Maybe you're right, but the first point, it theorically exactly is what you said, but I'm not sure whether it will do actually, I'm a noob in compiling. I also want to see what others say or maybe you could prove it. Thanks.

One thing to consider if you need no-capture discipline is writing it as an inner function instead, which cannot take captures.

fn this_compiles() {
  let mut a = 10;
  fn func(a: &mut i32) {...}
  func(&mut a);
}
7 Likes

I know, but sometimes it needs too much variables, it's inconvenient and redundant to write them in the arguments one by one, when calling them, we write them again. What I want is something to help me translate the closure to what you write automatically.

I don't think that exists.

The best you can do is make it a formal argument instead of a captured variable:

fn main() {
    let mut a = 10;
    let func = |a: &mut i32| {
        *a += 10;
    };
    a += 20;
    func(&mut a);
}

This isn't some limitation of closure ergonomics, it's unavoidable in safe code. You can see what happens if you force it to capture a mutable reference instead:

fn main() {
    let a = &mut 10;
    let mut func = || {
        *a += 10;
    };
    *a += 20;
    func();
}
error[E0503]: cannot use `*a` because it was mutably borrowed
 --> src/main.rs:6:5
  |
3 |     let mut func = || {
  |                    -- borrow of `*a` occurs here
4 |         *a += 10;
  |         -- borrow occurs due to use of `*a` in closure
5 |     };
6 |     *a += 20;
  |     ^^^^^^^^ use of borrowed `*a`
7 |     func();
  |     ---- borrow later used here

For more information about this error, try `rustc --explain E0503`.

Having multiple mutable borrows is invalid.

When I write code then I sometimes wish for something like that. But when I read code I'm really happy that you can not use closures or macros that way it Rust.

I guess in the end, since you read code more often than you write it, I'm glad Rust doesn't offer anything like that.

3 Likes

Very unique point of view. Do you mean write a formal function, then writing the arguments again and again is good for code maintenance? Indeed I have no much experience in Rust, but I wonder why people like this way. :thinking:

You don't need a formal function. A closure will work fine, but to do what you are asking requires a formal argument, not a captured variable (without using interior mutability; see below). This isn't a matter of Rust just being verbose, but a matter of code correctness. Since you mutate the variable both inside the closure/function body and outside but between calls to the closure, capturing is not a viable approach and a formal argument is the only way it can work. If you are concerned with having a lot of arguments, a common approach is to collect them into a common struct and pass it as a single argument.

Interior mutability:
If you really want to avoid any arguments to the closure, then interior mutability can work:

use std::cell::Cell;

fn main() {
    let foo = Foo { a: Cell::new(10) };
    let func = || {
        let old = foo.a.get();
        foo.a.set(old + 10);
    };
    let old = foo.a.get();
    foo.a.set(old + 20);
    func();
    println!("{}", foo.a.get());
}

struct Foo {
    a: Cell<i32>,
}

This requires that the wrapped type implements Copy. If it does not, then you can use RefCell which has a bit of runtime overhead cost. neither of these are safe to share between threads as they do not implement Sync.

1 Like

It's simply Python's explicit is better than implicit applied to the local variable.

As I have said: sometimes it's a PITA for the writer, but it's joy for the reader when you can just click on the variable in IDE and immediately see all the places where it can be changed. And can also see what other variables can be changed in that line.

It's similar to implicit use of this in C++ where people invent conventions like m_ prefix or _ suffix for member variables. Why do you think they are doing that? Use of self.var_name in Rust makes everything easily observable without such superfluous conventions.

Similarly here: if you find it tedious to pass dozens of variables into your closure then maybe it's time to think about how you can organize your structures better to not need that — instead of sweeping that complexity under the carpet like you would do in C++ or Java you need to think about how to group variables together to make sure there wouldn't be such a mess hidden from the view.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.