Llogiq on stuff

Closures as Anti-Lifetime-Gluteal-Bite-Device

Remember when reddit user The_Doculope warned me that “lifetime elision bites us in the ass”? It recently didn’t happen, but only narrowly, and today I want to share you the device I used to enact my escape: Closures.

The problem

Let’s say we want to do something with a borrow, but we need to get it first. So we write a function get_borrowed_foo(..) that gets us the needed foo. This will however not work, we will get an error (example modified from real code to protect the guilty):

src/foo.rs:548:30: 548:82 error: borrowed value does not live long enough
src/foo.rs:548                 &path.segments[0].identifier.name.as_str()
                               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
note: in expansion of if let expansion
src/foo.rs:546:9: 550:10 note: expansion site
src/foo.rs:545:59: 552:6 note: reference must be valid for the lifetime 't as defined on the block at 545:58...
src/foo.rs:545     ...lots of code here...

So what can we do? We cannot return the borrow here, because its lifetime is tied to something we cannot control. But we do have a way of wrapping a function so that we can freely use our borrow within our function: Closures.

Instead of get_borrowed_foo(..), we write a with_borrowed_foo(..) method, like:

fn with_borrowed_foo<F, T>(path: &Path, f: F) -> T
where F: FnOnce(&Foo) -> T {
  f(&path.segments[0].identifier.name.as_str())
}

Now we can call it with just about any closure that takes an immutable reference to a Foo and returns whatever, wrapping it in our with-function will ‘lift’ it to work on &Path instead of &Foo.

Note that I haven’t invented the technique, I just stole it from the syntax::codemap::CodeMap::with_expn_info(..) function.

Bonus: Redditor SimonSapin had a great solution involving explicit lifetimes which unfortunately turned out not to work. Yet. A similar solution may however work to solve related problems in other circumstances.

I also should note that I recently came across some more scary-looking lifetime errors, which came after a simple erroneous statement that closed a scope early. So if you see lifetime errors, don’t blindly grab a closure, look if there are other errors and fix them first.


What techniques do you use to get around ownership / borrowing issues? Discuss on reddit and rust-lang users