可以对 return 引用或拥有的值实施的特征方法

Trait method that can be implemented to either return a reference or an owned value

我正在尝试使用一种方法定义特征,该方法可以实现为 return 引用或拥有的值。

类似于:

struct Type;
trait Trait {
    type Value;
    fn f(&self) -> Self::Value;
}
impl Trait for () {
    type Value = Type;
    fn f(&self) -> Self::Value {
        Type
    }
}
impl Trait for (Type,) {
    type Value = &Type; // error[E0106]: missing lifetime specifier
    fn f(&self) -> Self::Value {
        &self.0
    }
}

虽然这段代码不起作用,因为 &Type 缺少生命周期说明符。我希望 &Type&self 具有相同的生命周期(即 fn f<'a>(&'a self) -> &'a Type),但我不知道如何在 Rust 中表达这一点。

我设法找到了几种方法来使这段代码工作,但我都不喜欢其中任何一种:

  1. 为特征本身添加明确的生命周期:

    trait Trait<'a> {
        type Value;
        fn f<'b>(&'b self) -> Self::Value where 'b: 'a;
    }
    impl<'a> Trait<'a> for () {
        type Value = Type;
        fn f<'b>(&'b self) -> Self::Value
            where 'b: 'a
        {
            Type
        }
    }
    impl<'a> Trait<'a> for (Type,) {
        type Value = &'a Type;
        fn f<'b>(&'b self) -> Self::Value
            where 'b: 'a
        {
            &self.0
        }
    }
    

    我不喜欢这个解决方案的地方是任何使用 Trait 的东西都需要一个明确的生命周期(我认为这不是本质上必需的),而且这个特性似乎实现起来不必要地复杂。

  2. 返回可能是也可能不是参考的内容 - 例如 std::borrow::Cow:

    trait Trait {
        type Value;
        fn f<'a>(&'a self) -> Cow<'a, Self::Value>;
    }
    impl Trait for () {
        type Value = Type;
        fn f<'a>(&'a self) -> Cow<'a, Self::Value> {
            Cow::Owned(Type)
        }
    }
    impl Trait for (Type,) {
        type Value = Type;
        fn f<'a>(&'a self) -> Cow<'a, Self::Value> {
            Cow::Borrowed(&self.0)
        }
    }
    

    我不喜欢这个解决方案的地方是 ().f() 是一个 Cow<_>:我需要调用 ().f().into_owned() 来获取我的 Type。这似乎是不必要的(并且在使用 Trait 作为特征对象时可能会导致一些可忽略的 运行 时间开销)。

    还要注意 Cow 不好,因为它要求 Self::Value 实现 ToOwned (thus, practically, Clone),这个要求太强了。无论如何,在没有这种限制的情况下,很容易实现 Cow 的替代方案。

这个问题还有其他解决办法吗? standard/most common/preferred 是什么?

这可以使用额外的关联对象来解决,以在 return 类型或引用之间进行选择,再加上一些元编程魔法。

首先,一些助手类型:

struct Value;
struct Reference;

trait ReturnKind<'a, T: ?Sized + 'a> {
    type Type: ?Sized;
}
impl<'a, T: ?Sized + 'a> ReturnKind<'a, T> for Value {
    type Type = T;
}
impl<'a, T: ?Sized + 'a> ReturnKind<'a, T> for Reference {
    type Type = &'a T;
}

ReturnKind 是一个 "type-level function",当 "input" 是 Value 时,return 是 T,而 &TReference.

然后是特征:

trait Trait {
    type Value;
    type Return: for<'a> ReturnKind<'a, Self::Value>;

    fn f<'a>(&'a self) -> <Self::Return as ReturnKind<'a, Self::Value>>::Type;
}

我们通过 "calling" 类型级函数 ReturnKind 生成 return 类型。

"input argument" Return 需要实现 trait 才能让我们写 <Return as ReturnKind<'a, Value>>。尽管我们不知道 Self 的生命周期究竟是多少,但我们可以使用 HRTB Return: for<'a> ReturnKind<'a, Value> 使 Return 受到 所有 可能生命周期的约束。 =32=]

用法:

impl Trait for () {
    type Value = f64;
    type Return = Value;

    fn f(&self) -> f64 {
        42.0
    }
}

impl Trait for (f64,) {
    type Value = f64;
    type Return = Reference;

    fn f(&self) -> &f64 {
        &self.0
    }
}

fn main() {
    let a: (f64,) = ( ().f(), );
    let b: &f64 = a.f();
    println!("{:?} {:?}", a, b);
    // (42,) 42
}

请注意,以上仅在 Value 类型具有 'static 生命周期时有效。如果 Value 本身有一个有限的生命周期,这个生命周期必须被 Trait 知道。由于 Rust doesn't support associated lifetimes yet,它必须像 Trait<'foo> 一样使用,不幸的是:

struct Value;
struct Reference;
struct ExternalReference;

trait ReturnKind<'a, 's, T: ?Sized + 'a + 's> {
    type Type: ?Sized;
}
impl<'a, 's, T: ?Sized + 'a + 's> ReturnKind<'a, 's, T> for Value {
    type Type = T;
}
impl<'a, 's, T: ?Sized + 'a + 's> ReturnKind<'a, 's, T> for Reference {
    type Type = &'a T;
}
impl<'a, 's, T: ?Sized + 'a + 's> ReturnKind<'a, 's, T> for ExternalReference {
    type Type = &'s T;
}

trait Trait<'s> {
    type Value: 's;
    type Return: for<'a> ReturnKind<'a, 's, Self::Value>;

    fn f<'a>(&'a self) -> <Self::Return as ReturnKind<'a, 's, Self::Value>>::Type;
}

impl Trait<'static> for () {
    type Value = f64;
    type Return = Value;

    fn f(&self) -> f64 {
        42.0
    }
}

impl Trait<'static> for (f64,) {
    type Value = f64;
    type Return = Reference;

    fn f(&self) -> &f64 {
        &self.0
    }
}

impl<'a> Trait<'a> for (&'a f64,) {
    type Value = f64;
    type Return = ExternalReference;

    fn f(&self) -> &'a f64 {
        self.0
    }

}

fn main() {
    let a: (f64,) = ( ().f(), );
    let b: &f64 = a.f();
    let c: &f64 = (b,).f();
    println!("{:?} {:?} {:?}", a, b, c);
    // (42,) 42 42
}

但是如果在 trait 上设置生命周期参数没问题,那么 OP 已经提供了一个更简单的解决方案:

trait Trait<'a> {
    type Value;
    fn f<'b>(&'b self) -> Self::Value where 'b: 'a;
}

impl<'a> Trait<'a> for () {
    type Value = f64;
    fn f<'b: 'a>(&'b self) -> Self::Value {
        42.0
    }
}

impl<'a> Trait<'a> for (f64,) {
    type Value = &'a f64;
    fn f<'b: 'a>(&'b self) -> Self::Value {
        &self.0
    }
}
impl<'a, 's> Trait<'s> for (&'a f64,) {
    type Value = &'a f64;
    fn f<'b: 's>(&'b self) -> Self::Value {
        self.0
    }
}

fn main() {
    let a: (f64,) = ( ().f(), );
    let b: &f64 = a.f();
    let c: &f64 = (b,).f();
    println!("{:?} {:?} {:?}", a, b, c);
    // (42,) 42 42
}

@kennytm 提出了一个很好的(如果复杂的话)解决方案;我想提出一个更简单的替代方案。

有两种可能为值提供生命周期名称:

  • 在特质层面:trait Trait<'a> { ... }
  • 在方法层面:trait Trait { fn f<'a>(&'a self) -> ... }

后者的语言支持不是很好,虽然更灵活但也更复杂。但是,也有前者通常就足够的情况;因此,不用多说,我向您介绍:

trait Trait<'a> {
    type Value;
    fn f(self) -> Self::Value;
}

f 消耗它的输出,如果 Self 是一个不可变的引用就没问题,因为那些是 Copy.

证据就在布丁里:

struct Type;

impl Trait<'static> for () {
    type Value = Type;
    fn f(self) -> Self::Value {
        Type
    }
}

impl<'a> Trait<'a> for &'a (Type,) {
    type Value = &'a Type;
    fn f(self) -> Self::Value {
        &self.0
    }
}

并且可以毫无问题地调用它:

fn main(){
   ().f();
   (Type,).f();
}

这个解决方案肯定没有那么灵活;但它也简单得多。