Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

"dropped here while still borrowed" when making lifetime explicits

I am trying to improve my understanding of rust borrow checker by making implicit lifetimes explicit. It actually came from a bigger problem for work, but I boiled it down to this (so far).

Let’s take this code as an example :

    struct StringWrapper<'a>(&'a str);
    struct StringWrapperWrapper<'a>(&'a StringWrapper<'a>);

    struct ContainingAValue {
        value: String,
    }

    impl ContainingAValue {
        fn do_something_with_wrapper<F>(&self, f: F)
        where
            F: FnOnce(StringWrapper) -> (),
        {
            let wrapper = StringWrapper(&self.value);
            f(wrapper)
        }

        fn do_something_with_wrapper_wrapper<F>(&self, f: F)
        where
            F: FnOnce(StringWrapperWrapper) -> (),
        {
            let wrapper = StringWrapper(&self.value);
            let tmp = StringWrapperWrapper(&wrapper);
            f(tmp)
        }
    }

This code compiles all right. Now, I want to make the lifetimes explicit in the implementation.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

    impl ContainingAValue {
        fn do_something_with_wrapper<'a, F>(&'a self, f: F)
        where
            F: FnOnce(StringWrapper<'a>) -> (),
        {
            let wrapper = StringWrapper(&self.value);
            f(wrapper)
        }

        fn do_something_with_wrapper_wrapper<'a, F>(&'a self, f: F)
        where
            F: FnOnce(StringWrapperWrapper<'_>) -> (),
        {
            let wrapper = StringWrapper(&self.value);
            let tmp = StringWrapperWrapper(&wrapper);
            f(tmp)
        }
    }

Until there, it also compiles all right.

Now, the big question : what lifetime should I put instead of '_ in the StringWrapperWrapper<'_> of do_something_with_wrapper_wrapper ?

I thought that this would work (line number added for reference in the error):

  17   │     fn do_something_with_wrapper_wrapper<'a, F>(&'a self, f: F)
  18   │     where
  19 ~ │         F: FnOnce(StringWrapperWrapper<'a>) -> (),
  20   │     {
  21   │         let wrapper = StringWrapper(&self.value);
  22   │         let tmp = StringWrapperWrapper(&wrapper);
  23   │         f(tmp)
  24   │     }

but I get :

   |
17 |     fn do_something_with_wrapper_wrapper<'a, F>(&'a self, f: F)
   |                                          -- lifetime `'a` defined here
...
21 |         let wrapper = StringWrapper(&self.value);
   |             ------- binding `wrapper` declared here
22 |         let tmp = StringWrapperWrapper(&wrapper);
   |                                        ^^^^^^^^
   |                                        |
   |                                        borrowed value does not live long enough
   |                                        this usage requires that `wrapper` is borrowed for `'a`
23 |         f(tmp)
24 |     }
   |     - `wrapper` dropped here while still borrowed

So, I tried to add a different lifetime :

  17 ~ │     fn do_something_with_wrapper_wrapper<'a: 'b, 'b, F>(&'a self, f: F)
  18   │     where
  19 ~ │         F: FnOnce(StringWrapperWrapper<'b>) -> (),
  20   │     {
  21   │         let wrapper = StringWrapper(&self.value);
  22   │         let tmp = StringWrapperWrapper(&wrapper);
  23   │         f(tmp)
  24   │     }

But get exactly the same error (with 'a being replaced by 'b).

The fact that I am using a FnOnce is important for my usecase and the error as this would compile :

    fn do_something_with_wrapper_wrapper<'a, F>(&'a self, f: F)
    where
        F: FnOnce(StringWrapperWrapper<'a>) -> (),
    {
        let wrapper = StringWrapper(&self.value);
        let tmp = StringWrapperWrapper(&wrapper);
        // f(tmp)
    }

>Solution :

This is a perfect usecase of higher rank trait bounds. The correct code should be

impl ContainingAValue {
   fn do_something_with_wrapper<'a, F>(&'a self, f: F)
    where
        F: for<'b> FnOnce(StringWrapper<'b>) -> (),
    {
        let wrapper = StringWrapper(&self.value);
        f(wrapper)
    }
    fn do_something_with_wrapper_wrapper<'a, F>(&'a self, f: F)
    where
        F: for<'b> FnOnce(StringWrapperWrapper<'b>) -> (),
    {
        let wrapper = StringWrapper(&self.value);
        let tmp = StringWrapperWrapper(&wrapper);
        f(tmp)
    }
}

The idea is that you expect f to work no matter the lifetime of the argument, which means in particular it will work with a lifetime bound by the scope of do_something_with_wrapper_wrapper, which cannot be named outside of do_something_with_wrapper_wrapper.

What you are expressing with

fn do_something_with_wrapper_wrapper<'a, F>(&'a self, f: F)
where
    F: FnOnce(StringWrapperWrapper<'a>) -> (),
{
    let wrapper = StringWrapper(&self.value);
    let tmp = StringWrapperWrapper(&wrapper);
    f(tmp)
}

is that the caller of do_something_with_wrapper_wrapper chooses the lifetime that f requires for its argument, which may be longer than the scope of do_something_with_wrapper_wrapper.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading