peoro
peoro

Reputation: 26060

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

I'm trying to define a trait with a method that can be implemented to either return a reference or an owned 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 piece of code doesn't work though, since &Type is missing a lifetime specifier. I'd want &Type to have the same lifetime as &self (i.e. fn f<'a>(&'a self) -> &'a Type), but I don't know how to express this in Rust.

I managed to find a couple of ways to make this code work, but I don't love either of them:

  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.

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

Upvotes: 5

Views: 2244

Answers (3)

Finomnis
Finomnis

Reputation: 22808

A couple of years later, this can now be solved using generic associated types:

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

Upvotes: 2

Matthieu M.
Matthieu M.

Reputation: 300209

@kennytm presented an excellent (if complex) solution; I wish to propose a much simpler alternative.

There are two possibilities to provide the lifetime name for the value:

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

The latter is not well supported by the language, and while more flexible is also quite more complicated. However, it also happens that the former is often enough; and thus without ado I present you:

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

f consumes its output, this is fine if Self is an immutable reference as those are 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 invoked without issue:

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

This solution is certainly not as flexible; but it's also significantly simpler.

Upvotes: 2

kennytm
kennytm

Reputation: 523624

This could be solved using an additional associated object to choose between whether to return a type or a reference, plus some meta-programming 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" which returns T when the "input" is Value, and &T for Reference.

And then the 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;
}

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

The "input argument" Return needs to implement the trait to allow us to write <Return as ReturnKind<'a, Value>>. Although we don't know what exactly the lifetime Self will be, we could make Return bound by all possible lifetime using HRTB Return: for<'a> ReturnKind<'a, Value>.

Usage:

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 Value type has 'static lifetime. If the Value itself has a limited lifetime, this lifetime has to be known by the Trait. Since Rust doesn't support associated lifetimes yet, it has to be used like 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 = &'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
}

But if having the lifetime parameter on the trait is fine, then OP 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
}

Upvotes: 8

Related Questions