`filter(func)` 和 `filter(|x| func(x))` 之间有什么区别?

5
什么是filter(|x| func(x))filter(func)之间的区别?也许一个好的起点是理解如何使用类似于filter(|x| func(x))的语法编写filter(func)。我的代码如下:
fn filter_out_duplicates(vec_of_vecs: Vec<Vec<u8>>) -> Vec<Vec<u8>> {
  vec_of_vecs
     .into_iter()
     .filter(all_unique)
     .collect()
}

pub fn all_unique<T>(iterable: T) -> bool
where
   T: IntoIterator,
   T::Item: Eq + Hash,
{
   let mut unique = HashSet::new();
   iterable.into_iter().all(|x| unique.insert(x))
}

error[E0599]: the method `collect` exists for struct `Filter<std::vec::IntoIter<Vec<u8>>, fn(&Vec<u8>) -> bool {tmp::all_unique::<&Vec<u8>>}>`, but its trait bounds were not satisfied
  --> src/main.rs:44:56
   |
44 |             vec_of_vecs.into_iter().filter(all_unique).collect()
   |                                                        ^^^^^^^ method cannot be called on `Filter<std::vec::IntoIter<Vec<u8>>, fn(&Vec<u8>) -> bool {tmp::all_unique::<&Vec<u8>>}>` due to unsatisfied trait bounds
   |
  ::: /.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/iter/adapters/filter.rs:15:1
   |
15 | pub struct Filter<I, P> {
   | ----------------------- doesn't satisfy `_: Iterator`
   |
   = note: the following trait bounds were not satisfied:
           `<fn(&Vec<u8>) -> bool {tmp::all_unique::<&Vec<u8>>} as FnOnce<(&Vec<u8>,)>>::Output = bool`
           which is required by `Filter<std::vec::IntoIter<Vec<u8>>, fn(&Vec<u8>) -> bool {tmp::all_unique::<&Vec<u8>>}>: Iterator`
           `fn(&Vec<u8>) -> bool {tmp::all_unique::<&Vec<u8>>}: FnMut<(&Vec<u8>,)>`
           which is required by `Filter<std::vec::IntoIter<Vec<u8>>, fn(&Vec<u8>) -> bool {tmp::all_unique::<&Vec<u8>>}>: Iterator`
           `Filter<std::vec::IntoIter<Vec<u8>>, fn(&Vec<u8>) -> bool {tmp::all_unique::<&Vec<u8>>}>: Iterator`
           which is required by `&mut Filter<std::vec::IntoIter<Vec<u8>>, fn(&Vec<u8>) -> bool {tmp::all_unique::<&Vec<u8>>}>: Iterator`
           which is required by `&mut Filter<std::vec::IntoIter<Vec<Placement>>, fn(&Vec<Placement>) -> bool {all_unique::<&Vec<Placement>>}>: Iterator`

但如果我使用|x| all_unique(x),代码就会编译通过。我知道在Rust中解密编译器错误是解决问题的推荐方式,但我觉得这个错误相当难以理解。
我发现讨论似乎更多地同情这个错误而不是解释强制转换,但我发现Rustonomicon中关于强制转换的章节太短了,无法提供理解。

谢谢。现在我明白了,在我的情况下,filter(|x| func(x))将隐式地取消引用x(它被转录为filter(|x:&Vec<u8>| func(*x))),但是为什么filter(func)会消耗变量而前者不会呢? filter(func)如何将vec_of_vecs中的值从引用后面移动并消耗它们? - financial_physician
(你的评论消失了,但我认为它们很有见地哈哈) - financial_physician
我的评论是不正确的,我尝试了一下你的例子,似乎完全是由其他原因引起的。对于造成的困惑,我很抱歉。 - mousetail
2
@mousetail 请查看playground,这是我认为的问题所在。它似乎与生命周期有关。 - YthanZhang
@YichyZhang 为什么我们会期望这2种不同的语法会改变生命周期呢? - financial_physician
相关问答:.map(f).map(|x| f(x))有什么区别?,但那里的解决方案实际上是由于类型强制转换引起的。 - kmdreko
2个回答

6
这个案例与强制转换无关。这是另一个晚期绑定与早期绑定寿命的案例。
Rust有两种生命周期:早期绑定和晚期绑定。区别在于决定使用哪个生命周期。
对于晚期绑定的生命周期,你会得到一个高级特质约束 - 类似于for<'a> fn(&'a i32)。然后,只有在调用函数时才会选择生命周期。
另一方面,对于早期绑定的生命周期,你会得到fn(&'some_concrete_lifetime i32)。生命周期可能被推断,有时可以省略,但它是存在的。而且必须在我们决定函数指针/项类型的同时决定。 filter()期望一个HRTB函数,即具有延迟绑定的生命周期。这是因为filter()中绑定的FnMut(&Self::Item) -> bool的展开形式是for<'a> FnMut(&'a Self::Item) -> bool,或者,如果您愿意,for<'a> FnMut<(&'a Self::Item,), Output = bool>
然而,您的all_unique()是针对T: IntoIterator的通用函数。如果我们设置T = &'a Vec<u8>,那么'a就是早期绑定的。这是因为来自通用参数的生命周期始终是早期绑定的 - 基本上,因为我们无法延迟绑定通用参数,因为在Rust中表达for<T>是不可能的,因为通用类型参数是单态化的,所以这通常是不可能的。

因此,如果我们揭示省略的生命周期,您想满足特质约束fn(&'some_lifetime Vec<u8>) -> bool: for<'all_lifetimes> FnMut(&'all_lifetimes Vec<u8>) -> bool,但这个约束是错误的。 这就是您看到的错误的原因。

然而,如果我们使用闭包,我们会生成一个特定于类型&'lifetime Vec<u8>的闭包。 由于它不是类型通用的,因此生命周期可以是晚期绑定的。


这是我长期以来一直在思考的问题的绝佳答案。传递函数和调用相同函数的闭包之间是否存在性能差异?如果有,那么可能是微不足道的。 - Holloway
2
@Holloway 如果它没有被内联,那么就会有影响。但很可能会被内联,如果没有影响,则性能影响很小。 - Chayim Friedman
感谢您的出色解释。我的理解是.filter(|f| all_unique(f))推迟了all_unique的单态化。这使得编译器首先确定f的后期绑定生命周期,然后才应该解决all_unique调用的通用(早期绑定)生命周期。 - aedm
@aedm 是的,基本上就是这样。 - Chayim Friedman
@Holloway 我会期望这样的闭包在发布版本中总是可以内联。虽然并没有保证这种内联一定会发生,但这种优化是“零成本抽象”的基石,并且被大量依赖着。 - user4815162342

0

我不完全确定这里发生了什么。根据你的观点,它甚至可以被视为编译器的限制/错误,但我认为这就是发生的事情:

当你写 filter(all_unique),其中 all_unique 是一个通用函数时,它会被解析为获取正在迭代的项目的引用,根据 filter 的定义:

fn filter<P>(self, predicate: P) -> Filter<Self, P>ⓘ where
    P: FnMut(&Self::Item) -> bool, 

所以你实际上是在调用all_unique<&Vec<u8>>。你可能认为一切都很好,因为&Vec<u8>实际上实现了IntoIterator和其他约束。

但问题在于生命周期。看到了吗,在filter中有一个约束P: FnMut(&Self::Item) -> bool,它实际上是P: for <'a> FnMut(&'a Self::Item) -> bool的语法糖,即函数必须能够接受任何生命周期,而你的函数却不能。

等等!你可能会说,你的函数all_unique<T>肯定可以接受任何生命周期'aT: 'a。这是正确的,但这不是正在发生的事情:你正在调用filter<P>,其中P=all_unique::<&'f Vec<u8>>'f那个特定的生命周期。而那个生命周期并不是任何生命周期!现在你的all_unique函数被染上了一个特定的生命周期,它不满足上面的for <'a> ...的要求。

当然,你实际上并不需要在这里使用for <'a>,因为你正在使用正确的生命周期调用函数,但语法就是这样,它强制产生错误。

显然的解决方案是编写all_unique以接受引用:

pub fn all_unique<T>(iterable: &T) -> bool

这实际上是语法糖:

pub fn all_unique<'a, T>(iterable: &'a T) -> bool

泛型参数 'a 的普适性是隐含的(即 for <'a> 的东西)。

现在调用 filter(all_unique) 选择了通用的 all_unique::<Vec<u8>>,它没有受到生命周期的污染,可以 用任何 'a 调用。

这确实是一种语法限制,你只能写成:

pub fn all_unique<T> (iterable: T) -> bool { /* ... */ }

pub fn all_unique_ref<'a, T> (iterable: &'a T) -> bool {
    all_unique::<&T>(iterable)
}

写成 filter(all_unique_ref) 会起作用,而 filter(all_unique) 则不会。

你使用 Lambda 表达式的解决方案:

filter(|x| all_unique(x))

就像 all_unique_ref 一样,但是匿名的。

TL;DR; 原始错误是由于参数的生命周期被捕获在泛型函数的类型中而不是在该函数的使用中所导致的。这使得 filter() 不高兴,因为它的参数看起来不够通用。


如果我实现 all_unique_ref 函数,那么会导致程序出错,因为 into_iter() 想要消耗掉 T,但是 T 并没有实现复制。 - financial_physician

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接