尝试将函数指针作为参数传递的借用检查器问题

Borrow checker issue trying to pass a function pointer as paramerter

我正在寻求帮助,以了解为什么借用检查器对于以下最小的非工作示例失败,我很乐意学习如何正确实现我正在尝试做的事情:

use std::collections::HashSet;

struct Foo {
    data: HashSet<usize>
}

impl Foo {
    fn test<'a, F, T>(&mut self, _operation: F) -> ()
    where F: Fn(&'a HashSet<usize>, &'a HashSet<usize>) -> T,
          T: Iterator<Item=&'a usize>
    {
        let update: HashSet<usize> = vec![4, 2, 9].into_iter().collect();
        self.data = _operation(&self.data, &update).copied().collect();
    }

    fn new() -> Self {
        Foo { data: HashSet::new() }
    }
}


fn main() {
    let mut foo: Foo = Foo::new();
    foo.test(HashSet::intersection);
}

我的主要困惑是,如果我用 HashSet::intersection 替换对 _operation 的调用,代码会编译。我认为参数 _operation 的类型将允许我在此处将 HashSet::intersectionHashSet::union 作为操作传递。

郑重声明,这是我收到的错误:

error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
  --> src\main.rs:13:32
   |
13 |         self.data = _operation(&self.data, &update).copied().collect();
   |                                ^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime defined on the method body at 8:23...
  --> src\main.rs:8:23
   |
8  |     fn test<'a, F, T>(&mut self, _operation: F) -> ()
   |                       ^^^^^^^^^
note: ...so that reference does not outlive borrowed content
  --> src\main.rs:13:32
   |
13 |         self.data = _operation(&self.data, &update).copied().collect();
   |                                ^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined on the method body at 8:13...
  --> src\main.rs:8:13
   |
8  |     fn test<'a, F, T>(&mut self, _operation: F) -> ()
   |             ^^
note: ...so that reference does not outlive borrowed content
  --> src\main.rs:13:32
   |
13 |         self.data = _operation(&self.data, &update).copied().collect();
   |                                ^^^^^^^^^^

For more information about this error, try `rustc --explain E0495`.
error: could not compile `aoc06` due to previous error

您传递的参数与您声明 Fn 绑定的生命周期不匹配。

fn test<'a, F, T>(&mut self, _operation: F) -> ()

'a 调用者可以指定的任意生命周期,

    F: Fn(&'a HashSet<usize>, &'a HashSet<usize>) -> T,

对于 _operation

的引用必须足够
        let update: HashSet<usize> = vec![4, 2, 9].into_iter().collect();
        self.data = _operation(&self.data, &update).copied().collect();

但是这里你从 self 借用(其生命周期未指定比 'a 长)和从 update 借用(这是一个局部变量,不能活得比'a).


为了正确地写这个,你需要指定 _operation 可以用 any 生命周期调用(因此包括局部变量借用的生命周期) .这很简单,就其本身而言:

    fn test<F, T>(&mut self, _operation: F) -> ()
    where
        F: for<'a> Fn(&'a HashSet<usize>, &'a HashSet<usize>) -> T,

请注意,'a 不再是 test 的生命周期参数。相反,它是 F 范围的一部分:您可以将此 for<'a> 符号理解为“for any lifetime, which我们将调用 'aF 可以作为带有引用的函数调用 &'a ...”。

但是,这实际上不是解决方案,因为您还有 T: Iterator<Item = &'a usize>,它再次使用 'a。目前不可能编写表达这种关系的 where 子句,特别是即使没有项目作为引用,迭代器也会借用 &'a HashSets.

这是当前 Rust 的一个不幸的限制——它还出现在尝试编写一个函数,该函数采用异步函数借用输入(这在结构上与您的情况相同,Future Iterator 的位置)。但是,有一个解决方法:您可以 为函数定义一个特征, 它有一个将所有内容链接在一起的生命周期参数。 (这不会对您的函数的调用者施加任何额外的工作,因为该特征是为所有合适的函数全面实现的。)

这是你的代码,添加了这样一个特征,并对 fn test():

进行了必要的修改
use std::collections::HashSet;

trait IteratorCallback<'a> {
    type Output: Iterator<Item = &'a usize> + 'a;
    fn call(self, a: &'a HashSet<usize>, b: &'a HashSet<usize>) -> Self::Output;
}

impl<'a, F, T> IteratorCallback<'a> for F
where
    F: FnOnce(&'a HashSet<usize>, &'a HashSet<usize>) -> T,
    T: Iterator<Item = &'a usize> + 'a,
{
    type Output = T;
    fn call(self, a: &'a HashSet<usize>, b: &'a HashSet<usize>) -> T {
        // Delegate to FnOnce
        self(a, b)
    }
}

struct Foo {
    data: HashSet<usize>,
}

impl Foo {
    fn test<F>(&mut self, _operation: F) -> ()
    where
        F: for<'a> IteratorCallback<'a>,
    {
        let update: HashSet<usize> = vec![4, 2, 9].into_iter().collect();
        self.data = _operation.call(&self.data, &update).copied().collect();
    }

    fn new() -> Self {
        Foo {
            data: HashSet::new(),
        }
    }
}

fn main() {
    let mut foo: Foo = Foo::new();
    foo.test(HashSet::intersection);
}

注意:我更改了绑定到 FnOnce 的函数,因为它比 Fn 更宽松,并且在这种情况下您需要的一切,但同样的技术也适用于 Fn 只要当您将 fn call(self, 更改为 fn call(&self,.

来源:我使用 this Reddit comment by user Lej77 作为特征技术的示例。

问题,正如(尽管是隐晦的)编译器消息所暗示的那样,存在生命周期不匹配:_operation 期望 HashSet 引用与 'a 一样长,但是&self.data 有生命周期 'b&mut self 的生命周期被省略,而 &update 有不同的生命周期,持续 test 函数体的持续时间。

要解决这个问题,我们必须指定函数类型 F 接收 HashMap 引用 任意 生命周期,而不仅仅是特定的生命周期 'a —— 这让编译器在调用 _operation 时推断出适当的生命周期。这就是为什么我们需要 Higher-Rank Trait Bounds (HRTBs):

fn test<F, T>(&mut self, _operation: F) -> ()
    where F: for<'a> Fn(&'a HashSet<usize>, &'a HashSet<usize>) -> T,

然而,这引发了另一个问题。我们如何将更高级别的生命周期 'a 应用于类型参数 T?不幸的是 Rust does not support higher-kinded types,但我们可以通过将函数类型 F 和更高级的类型 T “抽象”到一个特征和该特征的关联类型来逃脱。

trait Operation<'a, T: 'a> {
    type Output: Iterator<Item = &'a T>;
    fn operate(self, a: &'a HashSet<T>, b: &'a HashSet<T>) -> Self::Output;
}

Operation trait 表示对两个 HashSet 的操作,returns 一个迭代器引用,相当于函数 HashSet::unionHashSet::intersection,之类的。我们可以使用以下 impl 来实现这一点,它确保 HashSet::intersection 等实现 Operation:

impl<'a, T: 'a, I, F> Operation<'a, T> for F
where
    I: Iterator<Item = &'a T>,
    F: FnOnce(&'a HashSet<T>, &'a HashSet<T>) -> I,
{
    type Output = I;

    fn operate(self, a: &'a HashSet<T>, b: &'a HashSet<T>) -> Self::Output {
        self(a, b)
    }
}

然后我们可以在 Operation 特征上使用 HRTB,它不需要任何嵌套的更高类型:

fn test(&mut self, _operation: impl for<'a> Operation<'a, usize>) -> () {
    let update: HashSet<usize> = vec![4, 2, 9].into_iter().collect();
    self.data = _operation.operate(&self.data, &update).copied().collect();
    println!("{:?}", self.data);
}

Playground