调用一个函数,该函数使用不同的闭包进行两次闭包
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) 的闭包;
- 两次次调用闭包(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 == ']'
);
pp
与 use 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 类型(也不需要创建,因为可以编写库本身来利用它,完全避免复杂类型)。
作为一个学习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) 的闭包;
- 两次次调用闭包(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 == ']'
);
pp
与 use 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 类型(也不需要创建,因为可以编写库本身来利用它,完全避免复杂类型)。