Trait method that can be implemented for either link return or value

I am trying to define a trait with a method that can be implemented to either return a link or an existing value.

Something like:

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

      

This code snippet does not work as there is &Type

no lifetime specifier. I'd like it to &Type

have the same lifetime as &self

(i.e. fn f<'a>(&'a self) -> &'a Type

), but I don't know how to express that in Rust.

I managed to find several ways to get this code to work, but I don't like any of them:

  • 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 dislike about this solution is that any use Trait

    requires an explicit lifetime (which I don't think is inherently necessary), plus the trait seems unnecessarily difficult to implement.

  • Returning something that may or may not be a link - for example 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 about this solution is what ().f()

    - it is Cow<_>

    : I need to call ().f().into_owned()

    to get mine Type

    . This seems unnecessary (and can lead to some minor runtime overhead when used Trait

    as an object).

    Also note that Cow

    this is not a good one as it requires to Self::Value

    be implemented ToOwned

    (thus practically, Clone

    ) which is too large for the requirement. In any case, it is easy to implement an alternative Cow

    without such restrictions.

Are there any other solutions to this problem? What is the standard / most common / preferred?

+3


source to share


2 answers


This can be solved by using an additional bound object to choose whether to return a type or a reference, and some metaprogramming magic.

First, some helper types:

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

is a "type level function" that returns T

when Value

and &T

for Reference

.

And then the dash:

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

      

We produce the return type by "calling" a type-level function ReturnKind

.

The "input argument" Return

must implement the dash so we can write <Return as ReturnKind<'a, Value>>

. Even though we don't know what exactly is going to be for the Self, we could tie Return

every possible lifetime using HRTB Return: for<'a> ReturnKind<'a, Value>

.



Using:

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
}

      


Note that the above only works when the type Value

has 'static

a lifetime. If itself Value

has a limited lifetime, this lifetime must be known Trait

. Since Rust does not yet support bound lifetimes , it should be used as Trait<'foo>

, unfortunately:

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

      

But if the quality of life parameter in this value is accurate, then the OP has already provided an easier solution:

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
}

      

+4


source


@kennytm provided an excellent (if complex) solution; I want to offer a much simpler alternative.

There are two possibilities to provide a life name for a value:

  • at the feature level: trait Trait<'a> { ... }

  • at the method level: trait Trait { fn f<'a>(&'a self) -> ... }

The latter is poorly supported by the language, and more flexible is also more complex. However, it also happens that the former happens quite often; and thus without that I present to you:

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

      

f

consumes its result, it is ok if it Self

is an immutable reference, since Copy

.



The proof is in the pudding:

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

      

And it can be called without any problem:

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

      

This solution is, of course, not that flexible; but it is also much easier.

+2


source







All Articles