2017-04-10 66 views
3

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

喜欢的东西:

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

“为特质本身添加明确的生命期”正是我如何做的(尽管可能只有一个生命周期)。也许你可以进一步解释为什么你“相信不是本质上必要的”,因为对我来说它看起来非常必要 - 你*需要*将一生与“自我”类型联系起来,以防止在值出去之后使用引用的范围。 – Shepmaster

+0

@Shepmaster:哦,你的意思是'特质特质<'a> {type Value; fn f(&'a self) - > Self :: Value; ''?我确信我尝试了它,但没有奏效,但显然我错了。你是对的,阅读你的评论,并定义没有“b”的特质使我意识到“a”必须是特性的内在... – peoro

+1

是的,我会用'impl Trait <'static> for ){...}',但除此之外...... – Shepmaster

回答

4

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

首先,一些辅助类型:

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,和&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; 
} 

我们生产的返回类型由“调用”类型级功能ReturnKind

“输入参数”Return需要实现这个特性才能让我们编写<Return as ReturnKind<'a, Value>>。虽然我们不知道自己的寿命究竟是什么,但我们可以使用HRTBReturn: for<'a> ReturnKind<'a, Value>使Return约束全部可能的使用期限。

用法:

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知晓。由于锈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 
} 

但是,如果有对该性状寿命参数是好的,那么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(); 
} 

该解决方案肯定是不灵活;但它也显着简单。

+0

感谢您的建议!说实话,我相信我更喜欢使用'特质特质<'a> {type Value; fn f(&'a self) - > Self :: Value; }':它就像* complex *一样被用作你的建议(任何使用这个特性的东西在这两种情况下都需要一个明确的生命周期)并且不会消耗我更喜欢的'Self'。无论如何,很高兴看到像这样的替代方案可能更适合其他用例。 – peoro

+0

@peoro:请注意,在为'&'T'实现'Trait'的情况下,消费自己并不重要:'&'a T'是'Copy'。我会警惕'Trait <'a> {fn f(&'a self)}',你可能会不小心用这个“钉住”你的物体。相反,使用kennytm的解决方案来为'self'引入新的生命周期。 –

相关问题