Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do Rust closures work and how does it execute a closure?

Does it create a new thread and then execute that anonymous function inside the new thread?

I noticed many ownership / borrowing restrictions when I'm working with a closure. For example, if I have Fn(), I cannot pass a mutable variable inside the closure or it needs to be wrapped with a Mutex:

fn helloworld(f: &Fn(f64)) {
    f(42f64);
}

pub fn main() {
    let mut killer = 2;
    helloworld(&|n| {
        println!("{}", n);
        killer += 1;
    });
}

If a closure can be unsafe like that then something asynchronous or parallel is going on behind the scene and that's why Rust compiler doesn't let me to compile such code.

I might just be confused because I'm coming from a JavaScript / Python world and things are completely different there.

like image 555
Afshin Mehrabani Avatar asked Nov 30 '22 08:11

Afshin Mehrabani


1 Answers

There are two layers to this question.

First, a closure in Rust is just an anonymously-defined type that implements one or more "callable" traits. For example, this:

fn main() {
    let a = 6;
    let closure = |b| {
        println!("product is: {}", a * b);
    };
    closure(7);
}

is de-sugared into something similar to:

fn main() {
    let a = 6;
    let closure = {
        struct Closure<'a> {
            a: &'a i32,
        }
        impl<'a> Fn<(i32,)> for Closure<'a> {
            extern "rust-call" fn call(&self, (b,): (i32,)) {
                println!("product is: {}", (*self.a) * b);
            }
        }
        impl<'a> FnMut<(i32,)> for Closure<'a> {
            extern "rust-call" fn call_mut(&mut self, args: (i32,)) {
                self.call(args)
            }
        }
        impl<'a> FnOnce<(i32,)> for Closure<'a> {
            type Output = ();
            extern "rust-call" fn call_once(self, args: (i32,)) {
                self.call(args)
            }
        }
        Closure {
            a: &a,
        }
    };
    FnOnce::call_once(closure, (7,));
}

Note: the above code relies on unstable, internal details and will not work on a stable compiler. It is provided for explanation only; you should not use this pattern yourself.

There's no threading involved, and nothing magical is happening. They boil down to a regular function call with an extra initial "context" argument.

This brings us to the second layer, which is why your specific code doesn't work: because you told the compiler to forbid it. One critical concern for callables is how the context is passed to the callable's code. This is represented by the Fn, FnMut and FnOnce traits (which are explained in the answer to the question When does a closure implement Fn, FnMut and FnOnce?). By taking &Fn(f64), you've restricted yourself to only accepting closures which require immutable access to their context.

If you want a closure to be able to mutate its context, you need to use FnMut instead. Or, if you only need to call a closure once, you can use FnOnce (although not as a trait object like you're doing in your example).

like image 100
DK. Avatar answered Dec 15 '22 00:12

DK.