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

时间:2017-04-10 12:32:29

标签: reference rust traits lifetime

我尝试使用可以实现返回引用或拥有值的方法来定义特征。

类似的东西:

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(因此,实际上,Clone),这太强了需求。在没有这些限制的情况下,它很容易实现Cow的替代方案。

  3. 这个问题还有其他解决办法吗?什么是标准/最常见/首选的?

2 个答案:

答案 0 :(得分:5)

这可以使用额外的关联对象来解决,以选择是返回类型还是引用,以及一些元编程魔术。

首先,一些助手类型:

dbmodel.findOne(req.params.title, function(err, ) {
 records.picture.content = fs.readFileSync(req.files.path);
        records.picture.contentType = 'image/png';
        records.save(function (err) {
          if(!err) {
            res.send("Image is saved into MongoDB");

          } else {
            res.send(400).send();
          }
 })

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时会返回T,而Value会返回&T

然后是特质:

Reference

我们通过“调用”类型级函数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。虽然我们不知道Self的生命周期究竟是什么,但我们可以使用HRTB <Return as ReturnKind<'a, Value>>使所有生命周期Return绑定。{/ p>

用法:

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必须知道此生命周期。自Rust doesn't support associated lifetimes yet以来,不得不像Trait一样使用它:

Trait<'foo>

但是如果在特征上有生命周期参数就可以了,那么OP已经提供了一个更简单的解决方案:

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
}

答案 1 :(得分: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();
}

这个解决方案肯定不那么灵活;但它也明显更简单。