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

jte*_*epe 13 rust

在Rust 1.3.0中,Deref特征在文档中具有以下签名:

pub trait Deref {
    type Target: ?Sized;
    fn deref(&'a self) -> &'a Self::Target;
}
Run Code Online (Sandbox Code Playgroud)

我会在没有命名生命期的情况下实现它,因为它们无论如何都会被省略.但是,在docs示例中,它看起来像这样:

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);
}
Run Code Online (Sandbox Code Playgroud)

这一切都很好,但是如果我'a在impl而不是方法上指定了lifetime参数:

struct DerefExample<T> {
    value: T
}

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

    fn deref(&'a self) -> &'a T {
        &self.value
    }
}
Run Code Online (Sandbox Code Playgroud)

我收到以下错误:

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 | | }
   | |_^
Run Code Online (Sandbox Code Playgroud)

这让我很困惑.该方法的签名与文档中的签名没有什么不同.另外,我认为在impl上或在方法上直接指定life参数之间的区别仅在于参数的范围,因此它可以在整个impl块中使用,而不仅仅是在方法中使用.我在这里错过了什么?

Vla*_*eev 11

是,有一点不同.

该方法的签名与文档中的签名没有什么不同.

在docs中看起来像这样的事实是rustdoc的错误,并且已经解决了.

如果按[src]文档右上角的链接,您将被重定向到实际的源代码Deref,如下所示(我删除了额外的属性和注释):

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

您可以看到deref()声明具有生命周期参数.

我认为直接在impl或方法上指定lifetime参数之间的区别仅在于参数的范围.

这是错误的.差异不在范围内.我不认为我能够提供令人信服的并排示例,其中可以看到语义差异,但请考虑以下推理.

首先,生命周期参数与泛型类型参数没有区别.他们使用类似的声明语法并非巧合.与泛型参数一样,生命周期参数也参与方法/函数签名,因此如果要实现具有生命周期参数的方法的特征,则实现必须具有相同的生命周期参数(模可能重命名).

其次,impl签名中的生命周期参数用于表示与函数相关的不同种类的生命关系.对于方法,始终是调用者确定他们想要使用的实际生命周期参数.它再次类似于泛型方法的工作方式 - 调用者可以使用他们需要的任何类型实例化其类型参数.特别是,非常重要的Deref是 - 您可能希望实现的任何内容都Deref可以通过调用该方法的引用的生命周期取消引用,而不是其他内容.

impl,然而,寿命参数被选择时不使用这种参数的方法被调用,但当适当的impl是由编译器选择的.它可以根据值的类型(通常这样做)这样做,这使得用户无法在调用方法时指定任意生命周期.例如:

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

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

这里,该first_two()方法返回具有存储在Bytes结构内部的值的生存期的切片.方法的调用者无法决定他们想要的生命周期 - 它始终固定在调用此方法的结构内的切片的生命周期内.在保持相同的语义的同时,也不可能将life参数降低到方法,我想你可以看到原因.

在您的情况下,您指定的生命周期参数既不参与impl也不在任何关联类型的签名中,因此理论上可以使用它,就像它在每个函数上单独声明一样(因为它在调用方法时可以是任意的) ,但随后关于方法签名(上面提供)的推理开始了.