作为一个学习rust的项目,我正在编写一个程序,可以解析sgf文件(一种存储围棋游戏的格式,从技术上讲也是其他游戏)。目前该程序应该将类型的字符串(这只是一个示例)";B[ab]B[cd]W[ef]B[gh]"
解析为 [Black ((0,1)),黑色((2,3,)),白色((4,5)),黑色((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 完全陌生,所以我无法真正进一步隔离问题或在没有解析器组合器库的上下文中重新创建它(甚至可能与该库有关?)。
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 类型系统中的一个粗糙边缘(考虑到解析器组合器的闭包重性质,这并不是真正出乎意料的)。
这是您的问题的一个简化示例:
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)
。这正是错误所在。
最好的解决方案是使用函数而不是闭包,因为函数可以是通用的。不幸的是,您也无法做到这一点:您的闭包返回内部包含闭包的结构,例如
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
引入的。
这确实有效,但它真的很难看 - 我不得不使用编译器错误输出来实际确定所需的返回类型。功能稍有变化,就必须重新调整。理想情况下,这可以通过未装箱的抽象返回类型来解决——有 a postponed RFC在他们身上 - 但我们还没有做到这一点。
关于rust - 调用一个函数,该函数使用不同的闭包进行两次闭包,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28201792/