可以实现返回引用或拥有值的Trait方法

5

我想定义一个特质(trait),其中有一个方法可以实现返回引用或拥有的值。

例如:

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. Adding an explicit lifetime to the trait itself:

    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
        }
    }
    

    What I don't like of this solution is that anything using Trait needs an explicit lifetime (which I believe is not intrinsically necessary), plus the trait seems unnecessarily complicated to implement.

  2. Returning something that might or might not be a reference - like 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)
        }
    }
    

    What I don't like of this solution is that ().f() is a Cow<_>: I'd need to call ().f().into_owned() to obtain my Type. That seems unnecessary (and might result in some negligible run-time overhead when using Trait as a trait object).

    Also note that Cow is not good since it requires that Self::Value implements ToOwned (thus, practically, Clone), which is too strong of a requirement. It's anyways easy to implement an alternative to Cow without such constraints.

这个问题是否有其他解决方案?标准/最常见/首选方案是什么?


3
“将显式生命周期添加到特质本身”正是我会这样做的方式(尽管可能只需要一个生命周期)。也许你可以进一步解释为什么你认为它“本质上不是必要的”,因为对我来说,它似乎非常必要——您需要将生命周期与“Self”类型绑定,以防止在值超出作用域后继续使用引用。 - Shepmaster
1
是的,我会使用 impl Trait<'static> for () { ... },但除此之外... - Shepmaster
@Shepmaster 为什么?这是有功能上的区别,还是只是表达意图的问题?谢谢你,你在这里真的很有帮助。 - user1935361
@peoro:我正在尝试定义一个带有方法的特质,该方法可以实现返回引用或拥有值。 => 迭代器可以做到这一点,但关键问题是引用什么?它是指向 Self 内部的引用,还是指向 Self 引用的不相关对象? - Matthieu M.
@MatthieuM:嗯,两者都可以吧?在提供的例子中,它是对self内对象的引用(即&self.0),但我认为如果我想要impl<'a> Trait for (&'a Type,),那么引用可能会是self.0 - peoro
显示剩余5条评论
2个回答

8
这可以通过使用一个额外的关联对象来解决,以选择返回类型还是引用,再加上一些元编程魔法。
首先,需要一些辅助类型:
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 是一个“类型级函数”,当“输入”为 Value 时返回 T,而对于 Reference 则返回 &T

然后是 trait:

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;
}

我们通过“调用”类型级函数 ReturnKind 来生成返回类型。
“输入参数” Return 需要实现该特质,以使我们能够编写 <Return as ReturnKind<'a, Value>>。虽然我们不知道 Self 的生命周期具体是什么,但是我们可以使用 HRTB 使 Return 受到所有可能的生命周期的限制 Return: for<'a> ReturnKind<'a, Value>
用法:
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目前不支持关联生命周期,因此不幸地必须像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上拥有lifetime参数没有问题,那么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
}

2

@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();
}

这种解决方案肯定不够灵活,但也更为简单。

谢谢您的建议!老实说,我认为我更喜欢使用trait Trait<'a> { type Value; fn f( &'a self ) -> Self::Value; }的解决方案:它的使用方式与您所建议的方案一样复杂(任何使用该trait的内容都需要在两种情况下显式地指定生命周期),并且不会消耗Self,这是我更喜欢的。无论如何,很高兴看到像这样的替代方案,可能对其他用例更好。 - peoro
@peoro:请注意,在为&'a T实现Trait的情况下,消耗self并不重要:&'a T是可复制的。我会对Trait<'a> { fn f(&'a self) }持谨慎态度,因为这可能会意外地“固定”您的对象。最好使用kennytm的解决方案,为self引入一个新的生命周期。 - Matthieu M.

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