尝试将函数指针作为参数传递的借用检查器问题
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::intersection
和 HashSet::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我们将调用 'a
,F
可以作为带有引用的函数调用 &'a ...
”。
但是,这实际上不是解决方案,因为您还有 T: Iterator<Item = &'a usize>
,它再次使用 'a
。目前不可能编写表达这种关系的 where
子句,特别是即使没有项目作为引用,迭代器也会借用 &'a HashSet
s.
这是当前 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::union
、HashSet::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);
}
我正在寻求帮助,以了解为什么借用检查器对于以下最小的非工作示例失败,我很乐意学习如何正确实现我正在尝试做的事情:
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::intersection
和 HashSet::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我们将调用 'a
,F
可以作为带有引用的函数调用 &'a ...
”。
但是,这实际上不是解决方案,因为您还有 T: Iterator<Item = &'a usize>
,它再次使用 'a
。目前不可能编写表达这种关系的 where
子句,特别是即使没有项目作为引用,迭代器也会借用 &'a HashSet
s.
这是当前 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::union
、HashSet::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);
}