How to design the `Has` trait

I would like to construct a Has trait as demonstrated below:

trait Has<T> {
    fn get(&self) -> T;
}

impl<T> Has<&T> for T {
    fn get(&self) -> &T {
        self
    }
}

struct Foo(i32);

impl Has<i32> for Foo {
    fn get(&self) -> i32 {
        self.0
    }
}

playground

In its implementation, T can be borrowed from self, with a lifetime that is necessarily shorter than that of self. Alternatively, T can be separated from self, in which case, there are no imposed constraints on the lifetime of T.

But got the error:

error: `impl` item signature doesn't match `trait` item signature
 --> src/lib.rs:6:5
  |
2 |     fn get(&self) -> T;
  |     ------------------- expected `fn(&'1 T) -> &'2 T`
...
6 |     fn get(&self) -> &T {
  |     ^^^^^^^^^^^^^^^^^^^ found `fn(&'1 T) -> &'1 T`
  |
  = note: expected signature `fn(&'1 T) -> &'2 T`
             found signature `fn(&'1 T) -> &'1 T`
help: the lifetime requirements from the `impl` do not correspond to the requirements in the `trait`
 --> src/lib.rs:2:22
  |
2 |     fn get(&self) -> T;
  |                      ^ consider borrowing this type parameter in the trait
1 Like

This does not the thing you expect. Basically, it is

impl<T, 'a> Has<&'a T> for T {
    fn get(&self) -> &'a T {
        self
    }
}

where 'a = 'static is allowed, so this is a special case of your impl, and this is not what you intended.

impl<T> Has<&'static T> for T {
    fn get(&self) -> &'static T {
        self
    }
}

For this reason, the Deref trait has the &Target in the fn signature.
A trait Has that can do both owned and borrowed access could be quite tricky. But it definitely needs some GATs.

2 Likes

Maybe this? The where clauses are a bit weird, but seem to be part of the MVP for GATs

playground

#![feature(associated_type_defaults)]
trait Has<T> {
    type T1<'a> = T where Self: 'a;
    fn get(&self) -> Self::T1<'_>;
}

impl<T> Has<&T> for T {
    type T1<'a> = &'a T where T: 'a;
    fn get(&self) -> &'_ T {
        self
    }
}

struct Foo(i32);

impl Has<i32> for Foo {
    fn get(&self) -> i32 {
        self.0
    }
}

This approach does not mandate the associated type T1 to be identical to T...

Correct. Making it identical to T cannot work.
You want your ref-based impl to have the signature to effectively be fn get<'a>(&'a self) -> &'a T.

You cannot write impl<T> Has<&'always_lifetime_of_the_self_arg T> for T.
Even HRTB dont help here for<'a> &'a T is not valid and would basically mean &'static.

Maybe you can do some trait magic that says, "if T and T1 are different, they are not too different."

1 Like

That would be something like

trait Has<T> {
    fn get(self) -> T;
    //     ^^^^ not `&self`
}

impl<'a, T: ?Sized> Has<&'a T> for &'a T {
    fn get(self) -> &'a T {
        self
    }
}

struct Foo(i32);

impl Has<i32> for &Foo {
    fn get(self) -> i32 {
        self.0
    }
}

But if you use this your trait bounds may look like

fn foo<T: for<'any> Has<&'any str>>(_: &T) {}

You could just use the borrowing version always ala AsRef<_> and callers can copy or clone when needed.

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.