调用一个函数,该函数使用不同的闭包进行两次闭包

Calling a function which takes a closure twice with different closures

作为一个学习rust的项目,我正在写一个程序,可以解析sgf文件(一种存储围棋游戏的格式,技术上也是其他游戏)。目前该程序应该将 ";B[ab]B[cd]W[ef]B[gh]" 类型的字符串(这只是一个示例)解析为 [Black((0,1)),Black((2,3,)),White((4,5)),Black((6,7))]

为此,我使用了解析器组合器库。

我 运行 出现以下错误:

main.rs:44:15: 44:39 error: can't infer the "kind" of the closure; explicitly annotate it; e.g. `|&:| {}` [E0187]
main.rs:44      pmove().map(|m| {Property::White(m)})
                            ^~~~~~~~~~~~~~~~~~~~~~~~
main.rs:44:15: 44:39 error: mismatched types:
 expected `closure[main.rs:39:15: 39:39]`,
    found `closure[main.rs:44:15: 44:39]`
(expected closure,
    found a different closure) [E0308]
main.rs:44      pmove().map(|m| {Property::White(m)})
                            ^~~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to 2 previous errors
Could not compile `go`.

有问题的功能如下。我对 Rust 完全陌生,所以我无法真正进一步隔离问题或在没有 parser-combinators 库的上下文中重新创建它(甚至可能与该库有关?)。

fn parse_go_sgf(input: &str) -> Vec<Property> {
    let alphabetic = |&:| {parser::satisfy(|c| {c.is_alphabetic()})};
    let prop_value = |&: ident, value_type| {
        parser::spaces().with(ident).with(parser::spaces()).with(
            parser::between(
                parser::satisfy(|c| c == '['),
                parser::satisfy(|c| c == ']'),
                value_type
            )
        )
    };

    let pmove = |&:| {
        alphabetic().and(alphabetic())
        .map(|a| {to_coord(a.0, a.1)})
    };

    let pblack = prop_value(
        parser::string("B"),
        pmove().map(|m| {Property::Black(m)}) //This is where I am first calling the map function.
    );

    let pwhite = prop_value(
        parser::string("W"),
        pmove().map(|m| {Property::White(m)}) //This is where the compiler complains
    );

    let pproperty = parser::try(pblack).or(pwhite);

    let mut pnode = parser::spaces()
        .with(parser::string(";"))
        .with(parser::many(pproperty));

    match pnode.parse(input) {
        Ok((value, _)) => value,
        Err(err) => {
            println!("{}",err);
            vec!(Property::Unkown)
        }
    }
}

所以我猜这与所有具有不同类型的闭包有关。但在其他情况下,似乎可以使用不同的闭包调用相同的函数。例如

let greater_than_forty_two = range(0, 100)
                         .find(|x| *x > 42);
let greater_than_forty_three = range(0, 100)
                         .find(|x| *x > 43);

似乎工作得很好。

所以我的情况有所不同。

此外,由于我刚刚学习,也欢迎对代码提出任何一般性意见。

Rust 闭包的两个方面导致了您的问题,第一,闭包不能是通用的,第二,每个闭包都是它自己的类型。因为闭包不能是通用的,prop_value 的参数 value_type 必须是特定类型。因为每个闭包都是特定类型,所以您传递给 pwhite 中的 prop_value 的闭包与 pblack 中的闭包是不同的类型。编译器所做的是得出结论,value_type 必须具有 pblack 中的闭包类型,当它到达 pwhite 时,它会找到一个不同的闭包,并给出错误。

从您的代码示例来看,最简单的解决方案可能是使 prop_value 成为通用的 fn - 它看起来不需要闭包。或者,您可以将其参数 value_type 声明为闭包特征对象,例如&Fn(...) -> ...。这是一个演示这些方法的简化示例:

fn higher_fn<F: Fn() -> bool>(f: &F) -> bool {
    f()
}
let higher_closure = |&: f: &Fn() -> bool | { f() };

let closure1 = |&:| { true };
let closure2 = |&:| { false };
higher_fn(&closure1);
higher_fn(&closure2);
higher_closure(&closure1);
higher_closure(&closure2);

现有的答案很好,但我想分享一个更小的问题示例:

fn thing<F: FnOnce(T), T>(f: F) {}

fn main() {
    let caller = |&: f| {thing(f)};
    caller(|&: _| {});
    caller(|&: _| {});
}

当我们定义caller时,它的签名还没有完全固定。当我们第一次调用它时,类型推断会设置输入和输出类型。在此示例中,在第一次调用后,caller 将需要采用特定类型的闭包,即第一个闭包的类型。这是因为每个闭包都有自己独特的匿名类型。当我们第二次调用 caller 时,第二个闭包的(唯一的,匿名的)类型不适合!

正如@wingedsubmariner 指出的那样,没有办法用通用类型创建闭包。如果我们有像 for<F: Fn()> |f: F| { ... } 这样的假设语法,那么也许我们可以解决这个问题。做一个泛型函数的建议很好。

不幸的是,您偶然发现了 Rust 类型系统中的一个粗糙边缘(即,考虑到解析器组合器的闭包重性质,这并不是真的出乎意料)。

这是您的问题的简化示例:

fn main() {
    fn call_closure_fun<F: Fn(usize)>(f: F) { f(12) }  // 1
    fn print_int(prefix: &str, i: usize) { println!("{}: {}", prefix, i) }

    let call_closure = |&: closure| call_closure_fun(closure);  // 2

    call_closure(|&: i| print_int("first", i));  // 3.1
    call_closure(|&: i| print_int("second", i)); // 3.2
}

它给出的错误与您的代码完全相同:

test.rs:8:18: 8:47 error: mismatched types:
 expected `closure[test.rs:7:18: 7:46]`,
    found `closure[test.rs:8:18: 8:47]`
(expected closure,
    found a different closure) [E0308]
test.rs:8     call_closure(|&: i| print_int("second", i));
                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~

我们有(在代码中的注释中引用):

  1. 接受某种具体形式的闭包的函数;
  2. 一个用自己的参数调用函数 (1) 的闭包;
  3. 两次次调用闭包(1),每次都传递不同的闭包。

Rust 封口已拆箱。这意味着对于每个闭包,编译器都会生成一个新类型,它实现了一个闭包特征(Fn, FnMut, FnOnce)。这些类型是匿名的——它们没有您可以写出的名称。您所知道的是这些类型实现了某种特征。

Rust 是一种强类型和静态类型的语言:编译器必须在编译时知道每个变量和每个参数的确切类型。因此,它必须为您编写的每个闭包的每个参数分配类型。但是 (2) 的 closure 参数应该是什么类型呢?理想情况下,它应该是某种泛型类型,就像 (1) 中一样:闭包应该接受任何类型,只要它实现了一个特征。但是,Rust 闭包不能是通用的,因此没有语法来指定它。所以 Rust 编译器做它能做的最自然的事情——它根据 call_closure 的第一次使用推断 closure 参数的类型,即从 3.1 调用——也就是说,它分配匿名3.1!

中的闭包类型

但是这个匿名类型与3.2中闭包的匿名类型不同:它们唯一的共同点是都实现了Fn(usize)。这正是错误所在。

最好的解决方案是使用函数而不是闭包,因为函数可以是通用的。不幸的是,你也不能这样做:你的闭包 return 结构本身包含闭包,比如

pub struct Satisfy<I, Pred> { ... }

其中 Pred 后来被限制为 Pred: FnMut(char) -> bool。同样,由于闭包具有匿名类型,您不能在类型签名中指定它们,因此您将无法写出此类泛型函数的签名。

事实上,以下内容确实有效(因为我已经为 parser::satisfy() 参数调用提取了闭包):

fn prop_value<'r, I, P, L, R>(ident: I, value_type: P, l: L, r: R) -> pp::With<pp::With<pp::With<pp::Spaces<&'r str>, I>, pp::Spaces<&'r str>>, pp::Between<pp::Satisfy<&'r str, L>, pp::Satisfy<&'r str, R>, P>>
    where I: Parser<Input=&'r str, Output=&'r str>,
          P: Parser<Input=&'r str, Output=Property>,
          L: Fn(char) -> bool,
          R: Fn(char) -> bool {
    parser::spaces().with(ident).with(parser::spaces()).with(
        parser::between(
            parser::satisfy(l),
            parser::satisfy(r),
            value_type
        )
    )
}

你会像这样使用它:

let pblack = prop_value(
    parser::string("B"),
    pmove().map(|&: m| Property::Black(m)),
    |c| c == '[', |c| c == ']'
);

let pwhite = prop_value(
    parser::string("W"),
    pmove().map(|&: m| Property::White(m)),
    |c| c == '[', |c| c == ']'
);

ppuse parser::parser as pp 一起引入。

这确实有效,但它真的很难看 - 我不得不使用编译器错误输出来实际确定所需的 return 类型。功能稍有变化,就必须重新调整。理想情况下,这可以通过未装箱的抽象 return 类型来解决 - 它们上面有 a postponed RFC - 但我们还没有做到这一点。

作为 parser-combinators 的作者,我将用另一种方法来解决这个问题,而不需要使用编译器来生成 return 类型。

由于每个解析器基本上只是一个函数和 2 个关联类型,因此所有函数类型都有 Parser 特性的实现。

impl <I, O> Parser for fn (State<I>) -> ParseResult<O, I>
    where I: Stream { ... }
pub struct FnParser<I, O, F>(F);
impl <I, O, F> Parser for FnParser<I, O, F>
    where I: Stream, F: FnMut(State<I>) -> ParseResult<O, I> { ... }

一旦孤儿检查允许,这些都应该被单个特征替换并删除 FnParser 类型。与此同时,我们可以使用 FnParser 类型从闭包创建解析器。

使用这些特征,我们基本上可以隐藏 Vladimir Matveev 示例中的大型解析器类型 return。

fn prop_value<'r, I, P, L, R>(ident: I, value_type: P, l: L, r: R, input: State<&'r str>) -> ParseResult<Property, &'r str>
    where I: Parser<Input=&'r str, Output=&'r str>,
      P: Parser<Input=&'r str, Output=Property>,
      L: Fn(char) -> bool,
      R: Fn(char) -> bool {
    parser::spaces().with(ident).with(parser::spaces()).with(
        parser::between(
            parser::satisfy(l),
            parser::satisfy(r),
            value_type
        )
    ).parse_state(input)
}

我们现在可以用这个构建解析器

let parser = FnParser(move |input| prop_value(ident, value_type, l, r, input));

这基本上是我们目前使用 Rust 所能做的最好的事情。未装箱的匿名 return 类型将使所有这些变得更加容易,因为不需要复杂的 return 类型(也不需要创建,因为可以编写库本身来利用它,完全避免复杂类型)。