代码之家  ›  专栏  ›  技术社区  ›  Lukas Kalbertodt

添加不相关的泛型参数会触发奇怪的生存期错误

  •  7
  • Lukas Kalbertodt  · 技术社区  · 7 年前

    我有一个特性,我想为实现它的所有类型实现它 std::ops::Index . 此代码有效(如我所料):

    use std::ops::Index;
    use std::fmt::Display;
    
    
    trait Foo {
        fn foo(&self, i: usize) -> &Display;
    }
    
    impl<C> Foo for C 
    where
        C: Index<usize>,
        C::Output: Display + Sized,
    {
        fn foo(&self, i: usize) -> &Display {
            &self[i]
        }
    }
    

    ( Playground )

    然而 一旦我为我的特性引入一个通用参数,我会得到一个奇怪的终身错误。这是密码( Playground ):

    trait Foo<T> {
        fn foo(&self, i: T) -> &Display;
    }
    
    impl<C, T> Foo<T> for C 
    where
        C: Index<T>,
        C::Output: Display + Sized,
    {
        fn foo(&self, i: T) -> &Display {
            &self[i]
        }
    }
    

    奇怪的错误(显然是一个错误,在稍微不同的版本中重复了三次):

      error[E0311]: the associated type `<C as std::ops::Index<T>>::Output` may not live long enough
      --> src/main.rs:15:9
       |
    15 |         &self[i]
       |         ^^^^^^^^
       |
       = help: consider adding an explicit lifetime bound for `<C as std::ops::Index<T>>::Output`
    note: the associated type `<C as std::ops::Index<T>>::Output` must be valid for the anonymous lifetime #1 defined on the method body at 14:5...
      --> src/main.rs:14:5
       |
    14 | /     fn foo(&self, i: T) -> &Display {
    15 | |         &self[i]
    16 | |     }
       | |_____^
    note: ...so that the type `<C as std::ops::Index<T>>::Output` is not borrowed for too long
      --> src/main.rs:15:9
       |
    15 |         &self[i]
       |         ^^^^^^^^
    
    error[E0311]: the associated type `<C as std::ops::Index<T>>::Output` may not live long enough
      --> src/main.rs:15:9
       |
    15 |         &self[i]
       |         ^^^^^^^^
       |
       = help: consider adding an explicit lifetime bound for `<C as std::ops::Index<T>>::Output`
    note: the associated type `<C as std::ops::Index<T>>::Output` must be valid for the anonymous lifetime #1 defined on the method body at 14:5...
      --> src/main.rs:14:5
       |
    14 | /     fn foo(&self, i: T) -> &Display {
    15 | |         &self[i]
    16 | |     }
       | |_____^
    note: ...so that the type `<C as std::ops::Index<T>>::Output` will meet its required lifetime bounds
      --> src/main.rs:15:9
       |
    15 |         &self[i]
       |         ^^^^^^^^
    
    error[E0311]: the associated type `<C as std::ops::Index<T>>::Output` may not live long enough
      --> src/main.rs:15:10
       |
    15 |         &self[i]
       |          ^^^^^^^
       |
       = help: consider adding an explicit lifetime bound for `<C as std::ops::Index<T>>::Output`
    note: the associated type `<C as std::ops::Index<T>>::Output` must be valid for the anonymous lifetime #1 defined on the method body at 14:5...
      --> src/main.rs:14:5
       |
    14 | /     fn foo(&self, i: T) -> &Display {
    15 | |         &self[i]
    16 | |     }
       | |_____^
    note: ...so that the reference type `&<C as std::ops::Index<T>>::Output` does not outlive the data it points at
      --> src/main.rs:15:10
       |
    15 |         &self[i]
       |          ^^^^^^^
    

    我完全不理解这个错误。尤其是当错误提到 C::Output 据我所知 没有什么 处理附加参数 K

    有趣的是,不返回特征对象 &Display ,但将关联类型添加到 Foo 如果返回,将导致终身错误消失。( Playground )但是,这不是我的解决方案。


    这个错误是什么意思?这有道理吗?这是一个编译器错误吗?参数是什么 K 与…的一生有关 C::输出 ?

    1 回复  |  直到 7 年前
        1
  •  6
  •   trent oli_obk    7 年前

    这是有意义的,不是编译器的bug,但有点不方便。

    完整的解释

    可以实现 Index<T> 对于一种类型 C 这样的话 C::Output 有一个类型必须比 内部的 T . 下面是一个愚蠢的例子:

    struct IntRef<'a>(&'a i32);
    
    impl<'a, 'b: 'a> Index<IntRef<'a>> for IntRef<'b> {
        type Output = IntRef<'a>;
        fn index(&self, _: IntRef<'a>) -> &Self::Output {
            self
        }
    }
    

    毯子 impl Foo<IntRef<'a>> 对于 IntRef<'b> ,这是不健全的。要了解原因,请查看此非编译示例:

    let b = 2i32; // 'b begins here:
    let b_ref = IntRef(&b);
    let o: &Display;  // a reference that outlives 'a but not 'b
    {
        let a = 1i32; // 'a begins here:
        let a_ref = IntRef(&a);
    
        o = &b_ref[a_ref]; // <-- this errors: "a doesn't live long enough"
                           //     which is correct!
        o = b_ref.foo(a_ref); // <-- this wouldn't error, because the returned
                              //     value is `&'x (Display + 'x)` where 'x is
                              //     the lifetime of `b_ref`
    }
    println!("{:?}", o);
    

    o = &b_ref[a_ref]; 不会编译,因为 Index 实施方式如下: b_ref[a_ref] 不能生存 a_ref . 但是 o = b_ref.foo(a_ref) 必须 编译,自定义 Foo<T>::foo

    fn foo(&self, i: T) -> &Display                   // what you wrote
    fn foo<'a>(&'a self, i: T) -> &'a ('a + Display)  // what the compiler inferred
    

    …强制输出的生存期取决于 只有 在…的一生中 &self (见 this question )编译器拒绝 Foo 因为如果允许的话,你可以用它来“扩大”像 阿雷夫 在上面的例子中。

    (我想不出办法 IntRef 但事实是你 能够 去做吧。可能,由于内部的易变性,一个足够聪明的人可能会引入不健全,如果允许的话。)


    解决方案0:快速更新

    只需要那个 T 从不包含任何(非- 'static )证明人和你的工作完成了。

    impl<C, T> Foo<T> for C
    where
        T: 'static,
        C: Index<T>,
        C::Output: Display + Sized,
    

    这可能是最常见的 索引 但如果你想实现 Foo<&T> (这不是不合理的),你会想尝试一些不那么严格的方法。

    另一种可能性是需要 C::输出 成为 '静态 但这又比必要的更保守。

    解决方案1:最佳方法

    让我们回到 Foo::foo :

    fn foo<'a>(&'a self, i: T) -> &'a ('a + Display)
    

    注意这两个 'a S在 &'a ('a + Display) . 尽管它们是相同的,但它们代表不同的事物:被返回的引用的(最大)生存期,以及被引用事物中包含的任何引用的(最大)生存期。

    索引 ,这是我们用来实现的 ,返回的引用的生存期始终与 &自己 。但是 Self::Output 可能包含其他使用寿命不同(可能更短)的引用,这是整个问题。所以我们真正想写的是…

    fn foo(&self, i: T) -> &('a + Display)            // what you write
    fn foo<'b>(&'b self, i: T) -> &'b ('a + Display)  // what the compiler infers
    

    …它将 &自己 从任何可能属于 self::输出

    当然现在的问题是 A 特性中没有定义任何地方,因此我们必须将其添加为参数:

    trait Foo<'a, T> {
        fn foo(&self, i: T) -> &('a + Display);
    }
    

    现在你可以告诉铁锈了 C::输出 必须生存 A 对于 IMPL 申请,一切都会好起来的( playground ):

    impl<'a, C, T> Foo<'a, T> for C
    where
        C: Index<T>,
        C::Output: 'a + Display + Sized,
    {
        fn foo(&self, i: T) -> &('a + Display) {
            &self[i]
        }
    }
    

    解决方案2:对方法进行绑定

    解决方案1要求将生存期参数添加到 这可能是不可取的。另一种可能是添加 where 条款至 foo 这需要 T 比回来的人活得更久 &Display

    trait Foo<T> {
        fn foo<'a>(&'a self, i: T) -> &'a Display where T: 'a;
    }
    

    它有点笨拙,但实际上它让您将需求转移到功能上,而不是特性本身。缺点是 排除某些执行 通过坚持返回值在 T .