闭包何时实现Fn、FnMut和FnOnce?

217

什么条件可以使闭包实现Fn,FnMutFnOnce特性?

具体而言:

  • 何时闭包不会实现FnOnce特性?
  • 何时闭包不会实现FnMut特性?
  • 何时闭包不会实现Fn特性?

例如,在闭包内部修改其状态将使得编译器不能在其上实现Fn


19
你看过最近这篇关于 Rust 闭包的精彩文章吗?我建议你去看一下。 - Shepmaster
3个回答

225
每个特性代表了有关闭包/函数的越来越严格的属性,这些属性由其call_...方法的签名以及特别是self的类型指示:
  • FnOnce (self) 是只能调用一次的函数
  • FnMut (&mut self) 是需要&mut访问其环境才能被调用的函数
  • Fn (&self) 是只需要&访问其环境就可以被调用的函数

闭包|...| ...将自动实现它所能实现的内容。

  • 所有闭包都实现了FnOnce:不能被调用一次的闭包不会被称为闭包。请注意,如果闭包只实现了FnOnce,它只能被调用一次。
  • 不移出其捕获值的闭包实现了FnMut,允许它们被调用多次(如果有未别名化访问函数对象的方式)。
  • 不需要对其捕获值进行唯一/可变访问的闭包实现了Fn,允许在基本上任何地方调用它们。

这些限制直接遵循self的类型以及将闭包展开成结构体的“解糖”过程;详见我在博客文章Finding Closure in Rust中的描述。

有关闭包的信息,请参见The Rust Programming Language中的闭包:可以捕获其环境的匿名函数


13
我误读了 nalply 的评论,这让我感到有些困惑。未来的读者请注意,他说的是“如果一个闭包实现了 FnOnce”。 - sleeparrow
3
实现细节:尽可能自动实现并不完全正确,只有在需要时才会自动实现。您可以通过专门化来检测用于FnMut参数的闭包的缺失Fn-impl。这是 bug https://github.com/rust-lang/rust/issues/26085。 - bluss
4
即使闭包可以被多次调用,我仍然很难理解所有闭包都如何实现FnOnce。这个特质的名称令人困惑。 - Paul Razvan Berg
9
两种思考方式:
  1. FnOnce 中的“Once”指的是调用者将调用它的上限次数,而不是它可以被调用的次数。
  2. 您始终可以将可调用多次的闭包转换为只能调用一次的闭包:在第一次调用后简单地丢弃与闭包相关联的任何内存。但是您无法将其转换回到可多次调用的闭包。
- hjfreyer
1
你在这里谈论的是什么self?|| {}没有self。self从哪里来? - nikoss
显示剩余3条评论

4
鉴于目前只有一个其他答案,可能对问题的内容并不完全清楚,就像对我来说一样,我将提供一些例子以及一些推理,帮助我理解这些闭包特性的含义:
- 我们为什么需要它们?:“traits are how functions and structs can specify what kinds of closures they can use and store inside of them (as with the structs)”
- 它们各自表示闭包的什么信息:
  • Fn:

    • 不会将捕获值的所有权移出其作用域(例如,移交给闭包的调用者)。
    • 要么不捕获任何值,要么不修改捕获的值。
    • 由于它们不会影响所捕获值的底层内存的所有权或状态,Rust 允许多次调用这些闭包(因为调用它们是完全安全的操作)。
  • FnMut:

    • 不会将捕获值的所有权移出其作用域。
    • 实际上会从调用环境中捕获值并修改这些值的状态。(“”很重要,如果没有这个条件,我们将使用一个仅捕获值但不以任何方式改变它们的闭包,而这就是 Fn 特质的作用。正如 op 所指出的:“在闭包体上改变闭包状态会导致编译器不将其实现为 Fn。”)
  • FnOnce:

    • 实际上会将捕获值的所有权移出其作用域。
    • 要么不捕获任何值或者捕获并以不安全的方式使用它们,如果允许多次调用此函数将不安全(因为这可能导致对那些移出作用域的值的底层内存拥有多个所有者)。 (这里的“或者”很重要:所有闭包都实现了该特质,因为它们至少可以被调用一次,但如果只实现了该特质而没有其他特质,则它们只能被调用一次。)

      ^^ “什么时候一个闭包只实现了这个特质而没有其他特质?”:当它将捕获值的所有权移出其作用域时,因为根据其定义,如果闭包将捕获值的所有权移出其作用域,则其他两个特质不会由闭包实现。

(所有这些特征只是编译器确定在哪里可以使用任意闭包,同时保持闭包操作数据的内存处于“安全”状态的一种方式)

— 一些示例:

(注意:{{link1:我无法使用“impl”类型对“updated_vec”进行注释},所以我将在绑定定义附近的注释中指定由rust-analyzer推断的类型)

  • Fn 特性:
fn main() {
    let some_vec = vec![1, 3, 4];
    
    let get_that_same_vec = || { // "get_that_same_vec" type: impl Fn() -> &Vec<i32>
        &some_vec
        // as you can see the closure is specified to implement the *Fn* trait,
        // meaning it can be called however many times since it doesn't alter the data it operates on
        // or pass the ownership of that data to any other entity 
    };
    // - By using the "&" above we are basically saying that a closure  should just return a reference to a value.
    // - If we were to omit the "&" above, we would basically be saying:
    // "return and pass the ownership of this value to whomever decides to invoke this closure"
    // which would basically be like turning it into an infinite generator of that value and its ownership
    // "Oh, another caller wants this value and the ownership of it? Sure, let me take the copy of that value... out of thin air I guess!"
    // As you can figure, Rust doesn't allow for that last sentence to be true,
    // since that would allow multiple entities to have the ownership of the underlying memory,
    // which would eventually result in a "double free" error when needing to free that underlying memory when one of the owners goes out of scope. (see: *FnOnce* example)   

    println!("This is the vec: {:?}", get_that_same_vec());
    println!("This is the vec: {:?}", get_that_same_vec()); // if "&" would not be present above, then this would not compile
}
  • FnMut

(为什么我们需要用"mut"标记持有FnMut闭包的变量,请参阅这个很棒的答案)

fn main() {
    let mut some_vec = vec![1, 3, 4];

    let mut update_vec = || { // "update_vec" type: impl FnMut()
        some_vec.push(5); 
    };

    // As you can see the closures that implement the *FnMut* trait can be called multiple times,
    // because they do not pass the ownership of the data they capture out of their scope
    // they only alter its state, and if altering the value of its state multiple times is a legal operation
    // for a type on which the closure operates, then it is surely ok to call such a closure multiple times  
    update_vec();
    update_vec();
    println!("This is the updated \"some_vec\": {:?}", some_vec);
    // This is the updated "some_vec": [1, 3, 4, 5, 5]
}
  • FnOnce

(我在Fn示例中仅删除了闭包内部的"some_vec"前面的"&")

fn main() {
    let some_vec = vec![1, 3, 4];
    
    let get_that_same_vec = || { // "get_that_same_vec" type: impl FnOnce() -> Vec<i32>
        some_vec
        // as you can see the closure is specified to implement the *FnOnce* trait,
        // rust-analyzer shows only the most relevant trait that a closure implements
        // meaning that, in this case, a closure is marked as such that can only be called once,
        // since it passes the ownership of the data it captures to another entity.
        // In this case, that entity is the "get_that_same_vec" variable.
    };

    println!("This is the vec: {:?}", get_that_same_vec());
    // the call to println below does not compile and throws error "value used here after move",
    // and the way the compiler is able to infer this is by knowing
    // that a closure that implements only the `FnOnce` trait and no other trait
    // can only be called once, it no longer holds the ownership of a value it moved the ownership of the first time it was called.
    println!("This is the vec: {:?}", get_that_same_vec()); // this does not compile
}

0

这是针对 @huon 和 @svitanok 答案的简历,强调了 FnFnMut(以及其他类型)确实扩展了 FnOnce,但它们是分开使用的:

fn main() {
    let f: Box<dyn Fn()> = Box::new(|| { println!("foo"); });
    f();
    f();
    let mut f: Box<dyn FnMut()> = Box::new(|| { println!("foo"); });
    f();
    f();
    let f: Box<dyn FnOnce()> = Box::new(|| { println!("foo"); });
    f();
    f(); // compile error!
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接