实现一个 trait 方法,为拥有的类型返回一个有界的生命周期引用

问题描述

假设我有这个结构和这个特征:

#[derive(Debug)]
pub struct New<T>(T);

pub trait AsRefNew<'a> {
    fn as_ref(&self) -> New<&'a str>;
}

也就是说,AsRefNew trait 允许返回包含在 'a 新类型中的给定生命周期 New 的引用。此生命周期 'a 可能(并且将会)与 &self 参数的生命周期不同。

现在我可以为 New(&str) 实现这个特征,并使其输出的生命周期与包装的 &str 的生命周期相同:

impl<'a> AsRefNew<'a> for New<&'a str> {
    fn as_ref(&self) -> New<&'a str>{
        New(self.0)
    }
}

我的问题是我想为 New(String) 实现 trait,而这一次,我希望 'a 实际匹配 self 的生命周期。我的理解是这样的事情应该有效:

impl<'a> AsRefNew<'a> for New<String> where Self: 'a{
    fn as_ref(&self) -> New<&'a str> {
        New(self.0.as_str())
    }
}

除非它没有:

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src/main.rs:16:20
   |
16 |         New(self.0.as_str())
   |                    ^^^^^^
   |
note: first,the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 15:5...
  --> src/main.rs:15:5
   |
15 |     fn as_ref(&self) -> New<&'a str> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:16:13
   |
16 |         New(self.0.as_str())
   |             ^^^^^^
note: but,the lifetime must be valid for the lifetime `'a` as defined on the impl at 14:6...
  --> src/main.rs:14:6
   |
14 | impl<'a> AsRefNew<'a> for New<String> where Self: 'a{
   |      ^^
note: ...so that the expression is assignable
  --> src/main.rs:16:9
   |
16 |         New(self.0.as_str())
   |         ^^^^^^^^^^^^^^^^^^^^
   = note: expected `New<&'a str>`
              found `New<&str>`

我尝试了生命周期和泛型的不同变体,但我找不到更好的方式来表达我希望 'a'_ 匹配这一事实。

目标是让这个代码片段起作用:

fn main() {
    // This works:
    let a = String::from("hey");
    let b;
    {
        let c = New(a.as_str());
        b = c.as_ref().0;
    }
    println!("{:?}",b);
    
    // I would like that to work as well:
    let a = String::from("Ho");
    let b;
    let c = New(a);
    {
        b = c.as_ref().0;
    }
    println!("{:?}",b);
}

有什么想法吗?

解决方法

正如 Sven 所解释的那样,为了使这项工作发挥作用,我们需要两个不同的方法原型,而这对于定义的 AsRefNew 特征是不可能的。

仍然可以修改它以使小片段工作,例如在签名中引入第二个生命周期:

#[derive(Debug)]
pub struct New<T>(T);

pub trait AsRefNew<'b,'a> {
    fn as_ref(&'b self) -> New<&'a str>;
}

impl<'a> AsRefNew<'_,'a> for New<&'a str> {
    fn as_ref(&self) -> New<&'a str>{
        New(self.0)
    }
}

impl<'b,'a> AsRefNew<'b,'a> for New<String> where 'b:'a {
    fn as_ref(&'b self) -> New<&'a str> {
        New(self.0.as_str())
    }
}

impl<T> New<T> {
    pub fn test<'b,'a>(&'b self) -> New<&'a str> where Self: AsRefNew<'b,'a> {
        self.as_ref()
    }
}

以下代码段现在有效:

fn main() {
    // This works:
    let a = String::from("Hey");
    let b;
    {
        let c = New(a.as_str());
        b = c.as_ref().0;
    }
    println!("{:?}",b);
    
    // It now works
    let a = String::from("Ho");
    let b;
    let c = New(a);
    {
        b = c.as_ref().0;
    }
    println!("{:?}",b);
}

New 类型的方法的通用实现也是如此:

impl<T> New<T> {
    pub fn test<'b,'a> {
        self.as_ref()
    }
}

现在唯一的问题就是签名超级难看!我想知道 gats 是否可以使这更简单。

,

这真的不可能。 Trait 方法只能有一个原型,所有实现都必须匹配该原型。

对于 New<&'a str> 的情况,您的原型必须是您所拥有的

fn as_ref(&self) -> New<&'a str>;

另一方面,对于 New<String> 的情况,您需要

fn as_ref<'b>(&'b self) -> New<&'b str>;

相反,即返回值的生命周期需要与 self 引用的生命周期相关联。这个原型是不同的,因为生命周期是原型的一部分。

您尝试使用 trait bound Self: 'a 来解决这个问题是行不通的。类型 Self 在这里是 New<String>,它不包含任何引用,因此它实际上很简单地满足了 Self: 'static,因此它满足了任何生命周期限制。边界不会以任何方式进一步限制 Self 类型。您不能在 impl 级别限制 self 引用的生命周期,并且在函数定义级别对其进行限制将导致与 trait 定义中的原型背道而驰,如上所述。

出于同样的原因,您无法通过取消引用从 &'a str 获取 Cow<'a,str>。可能拥有 Cow,在这种情况下,返回的引用只能与用于取消引用的 self 引用一样长。