rust - 为什么这个 Rust 代码在 struct 上编译时带有生命周期限制,但如果边界仅在 impl 上,则会给出生命周期错误?

标签 rust lifetime

最近尝试写一段类似如下的代码:

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> 的一个实例, 其中 Fimpl 上声明的内容无关 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/

    相关文章:

    recursion - 如何使用 Read::take 递归地读取具有通用 Read 特征的树结构?

    Rust递归类型难度

    pointers - 将实现特征的数据存储在向量中

    rust - 从 HashSet 获取时发生不可变借用

    rust - 为什么将函数移至默认特征方法会导致借入错误?

    rust - Rust生命周期子类型不适用于Cell

    types - 如何修复 "implicitly returns '()' as its body has no tail or ' return'表达式?

    rust - 特征对象和特征的直接实现者的特征实现

    rust - 由于在 "thread '上出现了 'called ` main错误,导致无法创建通用JNIEnv结果:: unwrap()` on an ` Err`值

    iterator - 我该如何编写一个迭代器来返回对自身的引用?