Closures as Anti-Lifetime-Gluteal-Bite-Device

19 August 2015

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