【问题标题】:Rust nested closure moves and multiple ownersRust 嵌套闭包移动和多个所有者
【发布时间】:2021-09-01 13:42:45
【问题描述】:

这是我遇到的问题的 MWE,它无法编译:

use std::collections::HashSet;
use nom::{
    IResult,
    error::VerboseError,
    bytes::complete::is_not,
    character::complete::space1,
    combinator::map,
    multi::separated_list1,
};

type Set = HashSet<char>;
fn _make_parser(reducer: impl Fn(Set, Set) -> Set) -> impl Fn(&str) -> IResult<&str, Set, VerboseError<&str>> {
    move |input| {
        map(
            separated_list1(
                space1,
                is_not(" \t"),
            ),
            |vec: Vec<&str>| {
                vec.iter().map(|s| s.chars().collect::<Set>())
                    .reduce(reducer).unwrap()
            }
        )(input)
    }
}

fn main() {
    println!("Do nothing");
}

尽管不重要,_make_parser 旨在创建并返回一个 nom 解析器函数,该函数将采用空格分隔的字符串集,如下所示:

abc acdsodin ac azcefgd

然后解析器应将每个字符串转换为char 的集合(即HashSet),并在集合中应用一些操作,作为reducer 闭包传递给_make_parser。此闭包旨在调用类似HashSet::unionHashSet::intersection

代码编译失败,出现以下错误:

error[E0507]: cannot move out of `reducer`, a captured variable in an `FnMut` closure
  --> src/main.rs:21:33
   |
12 |     fn _make_parser(reducer: impl Fn(Set, Set) -> Set) -> impl Fn(&str) -> IResult<&str, Set, VerboseError<&str>> {
   |                     ------- captured outer variable
...
21 |                         .reduce(reducer).unwrap()
   |                                 ^^^^^^^ move occurs because `reducer` has type `impl Fn(Set, Set) -> Set`, which does not implement the `Copy` trait

error[E0507]: cannot move out of `reducer`, a captured variable in an `Fn` closure
  --> src/main.rs:19:17
   |
12 |     fn _make_parser(reducer: impl Fn(Set, Set) -> Set) -> impl Fn(&str) -> IResult<&str, Set, VerboseError<&str>> {
   |                     ------- captured outer variable
...
19 |                 |vec: Vec<&str>| {
   |                 ^^^^^^^^^^^^^^^^ move out of `reducer` occurs here
20 |                     vec.iter().map(|s| s.chars().collect::<Set>())
21 |                         .reduce(reducer).unwrap()
   |                                 -------
   |                                 |
   |                                 move occurs because `reducer` has type `impl Fn(Set, Set) -> Set`, which does not implement the `Copy` trait
   |                                 move occurs due to use in closure

很明显,所有操作都在_make_parser 函数中,并且我们有很多闭包正在进行。我们将_make_parser返回的闭包称为parser闭包,将传递给nommap组合器的闭包称为map闭包。

认为正在发生的事情是,reducer 闭包(或更准确地说是与闭包关联的隐式环境结构)被移动到更靠近解析器的位置,因为我们使用了 move 关键字.这就是我们想要的,因为我们需要 reducer 闭包来延长 _make_parser 函数的寿命。然后似乎reducer 也被移动到 map 闭包中,这也很好,因为除了 map 闭包之外,我们在解析器闭包中不需要它。似乎reduce 方法也试图获得reducer 的所有权(我试图从地图关闭中借用它,但它不会接受)。这似乎不应该是一个问题,因为 map 闭包实际上只被调用一次。然而,map 组合器采用的闭包类型是 FnMut,因为在某些情况下,它可能会被多次调用(例如,如果 map 组合器嵌套在重复组合器中)。我怀疑这是有问题的,因为 reducer 被移动到 reduce 方法中,这排除了多次调用 map 闭包。

我对错误消息的解释是否正确,还是我对情况的理解有误?如果是这样,如何解决?似乎可以使用引用计数来支持多个所有者来解决它,但是由于 reduce 需要一个闭包而不是 Rc&lt;closure&gt; 我无法让它工作。请注意,我只是在学习 Rust,还没有真正使用过引用计数器,尽管我认为我基本上了解它们是如何工作的。

最后,请注意,如果我将_make_parser 中的reducer 类型更改为普通的fn 而不是闭包Fn,我认为这是有道理的,因为在这种情况下没有隐式环境结构体担心移动。这甚至可以很好地用于我的实际目的,因为我实际上不需要在我的reducer 中捕获任何环境。但是,我读到接受闭包而不是普通函数更好(即更通用),而且我也很好奇如何使它与_make_parser 接受闭包一起工作,或者对于某些人来说这是不可能的原因。

【问题讨论】:

    标签: rust


    【解决方案1】:

    问题是您的 reducer 无法被复制或克隆,而是在另一个闭包中使用,因此 将其移出

    您的闭包move |input| {} 捕获reducer 并将其作为参数传递给reduce()。但如您所知,Rust 具有 move 语义,因此每次您将某些内容作为参数传递时,编译器都会对其进行move。因此,您的闭包只能被调用一次,但它的类型为Fn,根据合同要求它可以多次调用。

    因此,为了解决问题,您必须能够复制或克隆 reducer 闭包。鉴于您已经说过在您的情况下它可以是一个简单的fn,那么您可以简单地使它成为Copy

    use std::collections::HashSet;
    use nom::{
        IResult,
        error::VerboseError,
        bytes::complete::is_not,
        character::complete::space1,
        combinator::map,
        multi::separated_list1,
    };
    
    type Set = HashSet<char>;
    fn _make_parser(reducer: impl Copy + Fn(Set, Set) -> Set) -> impl Fn(&str) -> IResult<&str, Set, VerboseError<&str>> {
        move |input| {
            map(
                separated_list1(
                    space1,
                    is_not(" \t"),
                ),
                |vec: Vec<&str>| {
                    vec.iter().map(|s| s.chars().collect::<Set>())
                        .reduce(reducer).unwrap()
                }
            )(input)
        }
    }
    
    fn main() {
        println!("Do nothing");
    }
    

    或者,如果您从环境中捕获非Copy 类型,则可以将其设为Clone 以减少限制并将克隆作为参数传递给reduce.reduce(reducer.clone)

    Rust Playground Link

    【讨论】:

      猜你喜欢
      • 2019-02-27
      • 2016-11-17
      • 2021-10-21
      • 2021-10-07
      • 1970-01-01
      • 1970-01-01
      • 2020-05-30
      • 2013-09-05
      • 1970-01-01
      相关资源
      最近更新 更多