How can a closure using the `move` keyword create

2020-05-29 19:18发布

问题:

Up to this moment I thought that move |...| {...} would move variables inside a closure and the closure would implement only FnOnce, because you can move variables only once. To my surprise, however, I found that this code works:

extern crate futures;

use futures::stream;
use futures::stream::{Stream, StreamExt};
use std::rc::Rc;

#[derive(Debug)]
struct Foo(i32);

fn bar(r: Rc<Foo>) -> Box<Stream<Item = (), Error = ()> + 'static> {
    Box::new(stream::repeat::<_, ()>(()).map(move |_| {
        println!("{:?}", r);
    }))
}

fn main() {
    let r = Rc::new(Foo(0));
    let _ = bar(r);
}

Despite map having this signature:

fn map<U, F>(self, f: F) -> Map<Self, F>
where
    F: FnMut(Self::Item) -> U, 

It's surprising to me that a FnMut closure was created when using the move keyword and it even has 'static lifetime. Where can I find some details about move? Or how does it actually work?

回答1:

Yes, this point is quite confusing, and I think the wording of the Rust book contributes. After I read it, I thought the same as you did: that a move closure was necessarily FnOnce, and that a non-move closure was FnMut (and may also be Fn). But this is kind-of backwards from the real situation.

The closure can capture values from the scope where it's created. move controls how those values go into the closure: either by being moved, or by reference. But it's how they're used after they're captured that determines whether the closure is FnMut or not.

If the body of the closure consumes any value it captured, then the closure can only be FnOnce. After the closure runs the first time, and consumes that value, it can't run again.

As you've mentioned, you can consume a value inside the closure by calling drop on it, or in other ways, but the most common case is to return it from the closure, which moves it out of the closure. Here's the simplest example:

let s = String::from("hello world");
let my_fnonce = move || { s };

If the body of the closure doesn't consume any of its captures, then it's FnMut, whether it was move or not. If it also doesn't mutate any of its captures, it's also Fn; any closure that is Fn is also FnMut. Here's a simple example, albeit not a very good one.

let s = "hello world";
let my_fn = move || { s.length() }

Summary

The move modifier controls how captures are moved into the closure when it's created. FnMut membership is determined by how captures are moved out of the closure (or consumed in some other way) when it's executed.



回答2:

Up to this moment I thought that move |...| {...} will move variables inside closure and closure will implement only FnOnce, because you can move vars only once.

The variables are moved when the closure is created, not when it is invoked. Since you're only creating one closure, the move only happens once - regardless of how often map calls the function.



标签: rust