最近尝试写一段类似如下的代码:
pub struct Foo<'a, F> /* where F: Fn(&u32) -> bool */ {
u: &'a u32,
f: F
}
impl<'a, F> Foo<'a, F>
where F: Fn(&u32) -> bool
{
pub fn new_foo<G: 'static>(&self, g: G) -> Foo<impl Fn(&u32) -> bool + '_>
where G: Fn(&u32) -> bool
{
Foo { u: self.u, f: move |x| (self.f)(x) && g(x) }
}
}
这里是 Foo
的一个实例表示一条数据的条件(u32
),其中更严格的 Foo
可以通过 new_foo
从限制较少的一个构建,而不消耗旧的。然而,上面的代码并没有像编写的那样编译,而是给出了相当神秘的错误消息:error[E0308]: mismatched types
--> src/lib.rs:9:52
|
9 | pub fn new_foo<G: 'static>(&self, g: G) -> Foo<impl Fn(&u32) -> bool + '_>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
|
= note: expected type `std::ops::FnOnce<(&u32,)>`
found type `std::ops::FnOnce<(&u32,)>`
error: higher-ranked subtype error
--> src/lib.rs:9:5
|
9 | / pub fn new_foo<G: 'static>(&self, g: G) -> Foo<impl Fn(&u32) -> bool + '_>
10 | | where G: Fn(&u32) -> bool
11 | | {
12 | | Foo { u: self.u, f: move |x| (self.f)(x) && g(x) }
13 | | }
| |_____^
error: aborting due to 2 previous errors
经过大量实验,我确实找到了一种使代码编译的方法,并且我相信它会按预期运行。当声明可以在不依赖于这些边界的情况下编写时,我习惯于在 impls 上设置边界而不是声明,但由于某种原因取消注释 where
上面的子句,即复制绑定(bind)的F: Fn(&u32) -> bool
从 impl 到 Foo
的声明自己解决了问题。但是,我不知道为什么这会有所不同(我也不是首先真正理解错误消息)。有人对这里发生的事情有解释吗?
最佳答案
Rust 中唯一存在的子类型是生命周期,因此您的错误(神秘地)暗示存在某种生命周期问题。此外,错误清楚地指向了闭包的签名,这涉及两个生命周期:
'_
;和&u32
,您没有明确说明,因此可以推断出更高级别的生命周期,就好像您已经说明了以下内容:pub fn new_foo<G: 'static>(&self, g: G) -> Foo<impl for<'b> Fn(&'b u32) -> bool + '_>
where G: Fn(&u32) -> bool
使用上面更明确的签名会产生一个(非常)稍微有用的错误:
error[E0308]: mismatched types --> src/lib.rs:9:52 | 9 | pub fn new_foo<G: 'static>(&self, g: G) -> Foo<impl for<'b> Fn(&'b u32) -> bool + '_> | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other | = note: expected type `std::ops::FnOnce<(&'b u32,)>` found type `std::ops::FnOnce<(&u32,)>`
At least we can now see that "one type is more general than the other": we expected a closure that can take an argument with any lifetime but for some reason Rust thinks that what we have instead is a closure that takes an argument that may have some more restricted range of lifetimes.
What's happened? Well, the function's return value is the following expression:
Foo { u: self.u, f: move |x| (self.f)(x) && g(x) }
这当然是 struct Foo<'a, F>
的一个实例, 其中 F
与 impl
上声明的内容无关 block (具有其特征绑定(bind))。事实上,由于 F
上没有明确的界限在结构定义中,编译器必须完全推断此类型 F
从表达式本身。通过给结构定义一个特征绑定(bind),你告诉编译器 Foo
的实例,包括上面的表达式,有一个 F
实现 for<'b> Fn(&'b u32) -> bool
: 即 &u32
的生命周期范围论据是无界的。好的,所以编译器需要推断
F
相反,它确实推断它实现了Fn(&u32) -> bool
.然而,它还不够聪明,无法确定 &u32
的生命周期范围。论据可能受到限制。按照 @rodrigo's comment 中的建议添加显式类型注释上面指出,论点确实可以有任何生命周期。如果实际上对闭包参数的可能生命周期有一些限制,则需要通过更改
'b
的定义来更明确地指出这一点。从更高级别的特征绑定(bind)(即上面的返回类型中的 for<'b>
)到适合您情况的任何内容。希望一次chalk完全集成到编译器中,它将能够在不受限制和受限制的情况下执行此推断。与此同时,编译器在谨慎方面犯了错误,并没有做出潜在的错误假设。不过,这些错误肯定会更有帮助!
关于rust - 为什么这个 Rust 代码在 struct 上编译时带有生命周期限制,但如果边界仅在 impl 上,则会给出生命周期错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63721677/