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 isCow<_>
: I need to call().f().into_owned()
to get mineType
. This seems unnecessary (and can lead to some minor runtime overhead when usedTrait
as an object).Also note that
Cow
this is not a good one as it requires toSelf::Value
be implementedToOwned
(thus practically,Clone
) which is too large for the requirement. In any case, it is easy to implement an alternativeCow
without such restrictions.
Are there any other solutions to this problem? What is the standard / most common / preferred?
source to share
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
}
source to share
@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.
source to share