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