0

This is a follow up of "dropped here while still borrowed" when making lifetime explicits but can be looked at independently.

Now that @jthulhu made me discover the Higher-Rank Trait Bounds that fixed my previous error, I want to go a step further by generalizing it. So let's create a trait :

trait ToImplement {
    type Arg<'arg>;
    fn do_something<F>(&self, f: F)
    where
        F: for<'b> FnOnce(Self::Arg<'b>) -> ();
}

This trait can be implemented without an issue and, when use directly, works as expected as this test shows :

    impl ToImplement for String {
        type Arg<'arg> = &'arg str;
        fn do_something<F, T>(&self, f: F) -> T
        where
            F: for<'b> FnOnce(Self::Arg<'b>) -> T,
        {
            f(&self)
        }
    }

    #[test]
    fn works() {
        let hello = String::from("Hello");
        let r = hello.do_something(|s| format!("{s} world!"));
        assert_eq!(r,"Hello world!")
    }

Now, let's try to write a function that use the method do_something without knowing the implementer but with knowing some constraints on the Arg type so we can use it.

  10   │ fn go<D, I>(implementor: I)
  11   │ where
  12   │     D: Display,
  13   │     for<'a> I: ToImplement<Arg<'a> = D>,
  14   │ {
  15   │     implementor.do_something(|a| println!("{a}"));
  16   │ }

This does compile properly but if we try to use it like this :

  33   │     #[test]
  34   │     fn works() {
  35   │         let hello = String::from("Hello");
  36   │         go(hello);
  37   │     }

Then we get this error :

error[E0308]: mismatched types
   |
36 |         go(hello);
   |         ^^^^^^^^^ one type is more general than the other
   |
   = note: expected reference `&'a str`
              found reference `&str`
note: the lifetime requirement is introduced here
   |
13 |     for<'a> I: ToImplement<Arg<'a> = D>,
   |   

I think the way I declare the lifetime 'a on line 13 is wrong. But I have no idea on how else I could do it.

I read the rustonomicon chapter about ownership that @jthulhu pointed me to and thought that Borrow splitting would get me some answers and looked at the implementation of Take but there is no Higher-Rank Trait Bounds.

  • Please clarify your specific problem or provide additional details to highlight exactly what you need. As it's currently written, it's hard to tell exactly what you're asking. – Community Jul 08 '23 at 20:09

1 Answers1

1

After a lot of digging, I ended up finding a solution in the RFC : https://github.com/rust-lang/rfcs/blob/master/text/1598-generic_associated_types.md#using-associated-type-constructors-in-bounds

The trick is to declare the lifetime of Arg separately from ToImplement like this :

fn go<I>(implementor: I)
where
    I: ToImplement,
    for<'a> I::Arg<'a>: Display,
{
    implementor.do_something(|a| println!("{a}"));
}

Now, it looks like the compiler as issues to infer the types so we have to define it when calling the function :

    #[test]
    fn works() {
        let hello = String::from("Hello");
        go::<String>(hello);
    }