[从How do I write combinators for my own parsers in Rust?继续,我偶然发现了有关使用和/或产生函数/闭包的函数范围的问题。
[从这些slides中,我了解到为了方便消费者,您应该尝试将函数设为FnOnce
,并在可能的情况下返回Fn
。这为调用者提供了最大的自由,可以传递什么以及如何使用返回的函数。
在我的示例中,FnOnce
是不可能的,因为我需要多次调用该函数。在尝试使其编译时,我得出了两种可能性:
pub enum Parsed<'a, T> {
Some(T, &'a str),
None(&'a str),
}
impl<'a, T> Parsed<'a, T> {
pub fn unwrap(self) -> (T, &'a str) {
match self {
Parsed::Some(head, tail) => (head, &tail),
_ => panic!("Called unwrap on nothing."),
}
}
pub fn is_none(&self) -> bool {
match self {
Parsed::None(_) => true,
_ => false,
}
}
}
pub fn achar(character: char) -> impl Fn(&str) -> Parsed<char> {
move |input|
match input.chars().next() {
Some(c) if c == character => Parsed::Some(c, &input[1..]),
_ => Parsed::None(input),
}
}
pub fn some_v1<T>(parser: impl Fn(&str) -> Parsed<T>) -> impl Fn(&str) -> Parsed<Vec<T>> {
move |input| {
let mut re = Vec::new();
let mut pos = input;
loop {
match parser(pos) {
Parsed::Some(head, tail) => {
re.push(head);
pos = tail;
}
Parsed::None(_) => break,
}
}
Parsed::Some(re, pos)
}
}
pub fn some_v2<T>(mut parser: impl FnMut(&str) -> Parsed<T>) -> impl FnMut(&str) -> Parsed<Vec<T>> {
move |input| {
let mut re = Vec::new();
let mut pos = input;
loop {
match parser(pos) {
Parsed::Some(head, tail) => {
re.push(head);
pos = tail;
}
Parsed::None(_) => break,
}
}
Parsed::Some(re, pos)
}
}
#[test]
fn try_it() {
assert_eq!(some_v1(achar('#'))("##comment").unwrap(), (vec!['#', '#'], "comment"));
assert_eq!(some_v2(achar('#'))("##comment").unwrap(), (vec!['#', '#'], "comment"));
}
现在我不知道哪个版本是首选。版本1的使用Fn
不太通用,但是版本2的参数需要可变。
哪个更惯用/应该使用,其背后的原理是什么?
更新:感谢jplatte对版本1的建议。我在这里更新了代码,这种情况我觉得更有趣。
比较您编写它们时的some_v1
和some_v2
,我会说绝对应该首选版本2,因为它更通用。我想不出一个可以实现FnMut
而不是Fn
的解析闭包的好例子,但是parser
确实没有缺点mut
-正如您对问题的第一条评论所述,不会以任何方式限制呼叫者。
但是,有一种方法可以使版本1比版本2更通用(不是严格更通用,只是部分),即返回impl Fn(&str) -> …
而不是impl FnMut(&str) -> …
。这样,您将获得两个函数,每个函数在某种程度上都比另一个函数受到的约束更少,因此保留这两个函数甚至是有意义的: