How do Rust closures work and how does it execute

2019-06-24 06:58发布

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.

1条回答
闹够了就滚
2楼-- · 2019-06-24 07:21

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).

查看更多
登录 后发表回答