在impl或方法上指定生命周期参数有什么区别?

20
在 Rust 1.3.0 版本中,Deref trait 在文档中的签名如下:
pub trait Deref {
    type Target: ?Sized;
    fn deref(&'a self) -> &'a Self::Target;
}

我会实现它而不命名生命周期,因为它们最终会被省略。然而,在文档示例中,它看起来像这样:

use std::ops::Deref;

struct DerefExample<T> {
    value: T
}

impl<T> Deref for DerefExample<T> {
    type Target = T;

    fn deref<'a>(&'a self) -> &'a T {
        &self.value
    }
}

fn main() {
    let x = DerefExample { value: 'a' };
    assert_eq!('a', *x);
}

这很好地实现了功能,但如果我在impl上指定lifetime参数'a而不是在方法上指定,则会出现以下问题:
struct DerefExample<T> {
    value: T
}

impl<'a, T> Deref for DerefExample<T> {
    type Target = T;

    fn deref(&'a self) -> &'a T {
        &self.value
    }
}

我得到了以下错误:
error[E0308]: method not compatible with trait
  --> src/main.rs:10:5
   |
10 | /     fn deref(&'a self) -> &'a T {
11 | |         &self.value
12 | |     }
   | |_____^ lifetime mismatch
   |
   = note: expected type `fn(&DerefExample<T>) -> &T`
              found type `fn(&'a DerefExample<T>) -> &'a T`
note: the anonymous lifetime #1 defined on the method body at 10:5...
  --> src/main.rs:10:5
   |
10 | /     fn deref(&'a self) -> &'a T {
11 | |         &self.value
12 | |     }
   | |_____^
note: ...does not necessarily outlive the lifetime 'a as defined on the impl at 7:1
  --> src/main.rs:7:1
   |
7  | / impl<'a, T> Deref for DerefExample<T> {
8  | |     type Target = T;
9  | |
10 | |     fn deref(&'a self) -> &'a T {
11 | |         &self.value
12 | |     }
13 | | }
   | |_^

我感到困惑。这个方法的签名与文档中的方法没有什么不同。此外,我认为在实现或直接在方法上指定生命周期参数之间的区别仅在于参数的范围,因此它可以在整个实现块中使用而不仅仅是方法中。我在这里错过了什么吗?


1
这些回答不同的问题可能也会回答你的问题:https://dev59.com/LWAf5IYBdhLWcg3wQg1t 和 https://dev59.com/sF0a5IYBdhLWcg3wfIp-#30274925 - oli_obk
1个回答

19

是的,有所区别。

方法的签名与文档中的相同。

它在文档中呈现这种形式是 rustdoc 的错误,已经得到解决。

如果你点击文档右上角的 [src] 链接,你将被重定向到实际的 Deref 源代码,它看起来如下(我已经移除了额外的属性和注释):

pub trait Deref {
    type Target: ?Sized;
    fn deref<'a>(&'a self) -> &'a Self::Target;
}

你可以看到 deref() 声明了一个生命周期参数。

我曾认为在 impl 上指定生命周期参数和直接在方法上指定的区别仅仅在于作用域。

但这是错误的,不止是作用域不同。我无法列举出有明显语义差异的具体例子,但请考虑以下推理。

首先,生命周期参数与泛型类型参数没有区别。它们使用类似的声明语法也并非巧合。像泛型参数一样,生命周期参数参与方法/函数签名,所以如果你要实现一个带有生命周期参数方法的 trait,你的实现 必须 也有相同的生命周期参数(除了可能进行重命名)。

其次,impl 签名中的生命周期参数用于表达比函数中不同种类的生命周期关系。对于方法,实际的生命周期参数永远是调用者确定的。再次类比泛型方法--调用者可以将其类型参数实例化为任何需要的类型。对于 Deref 特别重要的是 -- 你希望任何实现 Deref 的内容均可以使用该方法被引用的生命周期进行引用获取,而不是其他生命周期。

然而,在 impl 中,当编译器选择合适的 impl 时,并不是在调用使用该参数的方法时选择生命周期参数,而是选择 impl 时就确定。通常情况下,它会基于值得类型进行选择,这可以避免用户在调用方法时指定任意的生命周期。例如:

struct Bytes<'a>(&'a [u8]);

impl<'a> Bytes<'a> {
    fn first_two(&self) -> &'a [u8] {
        &self.0[..2]
    }
}

在这里,first_two() 方法返回一个具有与存储在 Bytes 结构体内部的 slice 相同生命周期的切片。该方法的调用者无法决定他们想要哪个生命周期 - 它总是固定为该方法所调用的结构体内切片的生命周期。同时,如果保持相同语义,无法将生命周期参数传递到方法中,我想你可以看出原因。

在您的情况下,您指定的生命周期参数既不参与 impl 的签名,也不参与任何关联类型,因此在每个函数上它理论上可以被使用(因为在方法调用时它可以是任意的),但是接着就会触发上面提供的关于方法签名的推断。


非常详细并且讲解得非常好。谢谢。 - jtepe
我仍然不理解:如果我将生命周期限制为“self”,为什么它与出现在“impl”子句中的生命周期不同? - Chayim Friedman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接