使用闭包作为参数和 return 值,Fn 或 FnMut 更符合习惯?

With closures as parameter and return values, is Fn or FnMut more idiomatic?

How do I write combinators for my own parsers in Rust? 继续,我无意中发现了这个问题,涉及消耗 and/or 产生 functions/closures.

的函数边界

从这些slides中了解到,为了方便消费者,尽量把函数FnOnce,return尽量Fn。这给了调用者最大的自由来传递什么以及用 returned 函数做什么。

在我的示例中,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"));
}

playground

现在我不知道应该首选哪个版本。版本 1 采用 Fn 不太通用,但版本 2 需要其参数可变。

哪个更idiomatic/should被使用,背后的原理是什么?


更新:感谢对第一版的建议。我在这里更新了代码,我发现这种情况更有趣。

比较 some_v1some_v2 你写的时候我会说版本 2 应该是首选,因为它更通用。我想不出一个很好的解析闭包的例子,它会实现 FnMut 而不是 Fn,但是 parsermut 确实没有缺点 - 如中所述对您的问题的第一条评论不会以任何方式限制来电者。

但是,有一种方法可以使版本 1 比版本 2 更通用(严格来说不是更通用,只是部分通用),那就是 returning impl Fn(&str) -> … 而不是impl FnMut(&str) -> …。通过这样做,您将获得两个函数,每个函数在某种程度上都比另一个函数受到的约束更少,因此保留两个函数甚至可能有意义:

  • 具有 return 类型更改的版本 1 在其参数(可调用对象不能改变其关联数据)中的限制更为严格,但在其 return 类型中的限制较少(您保证returned callable 不会改变其关联数据)
  • 版本 2 对其参数的限制较少(允许可调用对象改变其关联数据)但对其 return 类型的限制更为严格(returned 可调用对象可能会改变其关联数据)