If I have `trait Trait<T>` where T is passed as parameter to closure, how do I implement it when T should be a reference?

Hello!

Here's a simplified example:

use std::marker::PhantomData;

trait Foo<T> {
    fn do_thing(&self, cb: impl FnOnce(&T));
}

struct Bar<T>(PhantomData<T>);

impl<T> Bar<T> {
    fn new() -> Self {
        Self(PhantomData)
    }
}

impl<T: Default> Foo<&T> for Bar<T> {
    fn do_thing(&self, cb: impl FnOnce(&&T)) {
        cb(&&T::default());
    }
}

fn main() {
    Bar::<i32>::new().do_thing(|_| {});
}

which fails to compile with

error[E0276]: impl has stricter requirements than trait
  --> src/main.rs:16:33
   |
4  |     fn do_thing(&self, cb: impl FnOnce(&T));
   |     ---------------------------------------- definition of `do_thing` from trait
...
16 |     fn do_thing(&self, cb: impl FnOnce(&&T)) {
   |                                 ^^^^^^^^^^^ impl has extra requirement `for<'a, 'b> impl FnOnce(&&T): FnOnce<(&'a &'b T,)>`

For more information about this error, try `rustc --explain E0276`.

Trying to impl<'a, ... doesn't solve issue, just moves lifetime errors around and changes them.

Is there some idiomatic way to describe such implementation. Or should I resort to something like GATs?

EDIT: Just for the reference, I know that implementing against plain T works. That's not what I need.

Thanks

The problem is that you are trying to express that the callback must take references of arbitrary lifetime, but the impl can only work for some caller-chosen lifetime. The trait declares that the lifetime of the reference taken by the callback is unrelated to (independent of) any lifetime annotations of T. I believe this trait can't express what you want.

Perhaps consider introducing an explicit lifetime dependence in the trait?

2 Likes

Eliding lifetimes in the impl header means they shouldn't matter in the implementation, but here they do matter because of how the type parameter is used.

trait Foo<T> {
//        ^    These have to match      v
    fn do_thing(&self, cb: impl FnOnce(&T));
}

impl<T: Default> Foo<&T> for Bar<T> {
//                   ^^ Think of this as "for any &'b T"
// But this is generic over the inner
// lifetime and thus doesn't match 'b   vv
    fn do_thing(&self, cb: impl FnOnce(&&T)) {

That's what the "extra requirement" is referring too; callers of this implementation would have to satisfy something more demanding than what the trait definition said.


Using an explicit lifetime reveals that there's an actual problem with your method body: you're asking the caller to supply a closure that operates on a specific lifetime. But if the caller can name that lifetime, it's longer than the method call, and you can't borrow a local for that long.

(Commenting out the body compiles.)


You could have an associated type which is T for both owned T and &T. That allows you to get the higher-ranked bound in the impl FnOnce(&_) in the right spot for both cases.

2 Likes

Thanks.
Seems what I need requires smth like Trait<for <'a> &'a T>
I guess I gotta figure out some other way to describe this.

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.