Reputation: 3636
I have the following function:
pub fn map_option<A: 'static, B: 'static> (a2b: Box<Fn(A) -> B>) -> Box<Fn(Option<A>) -> Option<B>> {
Box::new(move |opt_a: Option<A>| {
opt_a.map(|a| a2b(a))
})
}
However, this was quite difficult to write. I started off with something simpler that didn't work, but I don't understand why it didn't work.
Here is my first version:
pub fn map_option_1<A: 'static, B: 'static> (a2b: Box<Fn(A) -> B>) -> Box<Fn(Option<A>) -> Option<B>> {
Box::new(|opt_a: Option<A>| {
opt_a.map(a2b)
})
}
This gave me the following error:
error[E0507]: cannot move out of `a2b`, a captured variable in an `Fn` closure
--> src/lib.rs:11:19
|
9 | pub fn map_option_1<A: 'static, B: 'static> (a2b: Box<Fn(A) -> B>) -> Box<Fn(Option<A>) -> Option<B>> {
| --- captured outer variable
10 | Box::new(|opt_a: Option<A>| {
11 | opt_a.map(a2b)
| ^^^ move occurs because `a2b` has type `std::boxed::Box<dyn std::ops::Fn(A) -> B>`, which does not implement the `Copy` trait
I thought that I might need to move
the closure, so that it gets ownership of a2b
:
pub fn map_option_2<A: 'static, B: 'static> (a2b: Box<Fn(A) -> B>) -> Box<Fn(Option<A>) -> Option<B>> {
Box::new(move |opt_a: Option<A>| {
opt_a.map(a2b)
})
}
However, this also didn't work. It failed with the following message:
error[E0507]: cannot move out of `a2b`, a captured variable in an `Fn` closure
--> src/lib.rs:17:19
|
15 | pub fn map_option_2<A: 'static, B: 'static> (a2b: Box<Fn(A) -> B>) -> Box<Fn(Option<A>) -> Option<B>> {
| --- captured outer variable
16 | Box::new(move |opt_a: Option<A>| {
17 | opt_a.map(a2b)
| ^^^ move occurs because `a2b` has type `std::boxed::Box<dyn std::ops::Fn(A) -> B>`, which does not implement the `Copy` trait
This error message says that a2b
doesn't implement Copy
, which I guess makes sense, but I couldn't figure out how to fix it.
Out of desperation, I tried the following:
pub fn map_option_3<A: 'static, B: 'static> (a2b: Box<Fn(A) -> B>) -> Box<Fn(Option<A>) -> Option<B>> {
Box::new(|opt_a: Option<A>| {
opt_a.map(|a| a2b(a))
})
}
This at least gave me a different error:
error[E0373]: closure may outlive the current function, but it borrows `a2b`, which is owned by the current function
--> src/lib.rs:22:14
|
22 | Box::new(|opt_a: Option<A>| {
| ^^^^^^^^^^^^^^^^^^ may outlive borrowed value `a2b`
23 | opt_a.map(|a| a2b(a))
| --- `a2b` is borrowed here
|
note: closure is returned here
--> src/lib.rs:22:5
|
22 | / Box::new(|opt_a: Option<A>| {
23 | | opt_a.map(|a| a2b(a))
24 | | })
| |______^
help: to force the closure to take ownership of `a2b` (and any other referenced variables), use the `move` keyword
|
22 | Box::new(move |opt_a: Option<A>| {
| ^^^^^^^^^^^^^^^^^^^^^^^
The problem with ownership makes sense, I guess. This is what led me to the solution above that actually works.
One other thing I tried that didn't work is the following:
pub fn map_option_4<A: 'static, B: 'static> (a2b: Box<Fn(A) -> B>) -> Box<Fn(Option<A>) -> Option<B>> {
Box::new(|opt_a: Option<A>| {
opt_a.map(move |a| a2b(a))
})
}
This gave me the following error:
error[E0507]: cannot move out of `a2b`, a captured variable in an `Fn` closure
--> src/lib.rs:29:19
|
27 | pub fn map_option_4<A: 'static, B: 'static> (a2b: Box<Fn(A) -> B>) -> Box<Fn(Option<A>) -> Option<B>> {
| --- captured outer variable
28 | Box::new(|opt_a: Option<A>| {
29 | opt_a.map(move |a| a2b(a))
| ^^^^^^^^ ---
| | |
| | move occurs because `a2b` has type `std::boxed::Box<dyn std::ops::Fn(A) -> B>`, which does not implement the `Copy` trait
| | move occurs due to use in closure
| move out of `a2b` occurs here
Here's a playground with each of these functions.
I don't think I have a good enough understanding of lifetimes and ownership to see why each of these functions fail.
I can sort of understand how map_option_1
and map_option_3
fail, because move
is not being used to explicitly move ownership, but I am surprised that map_option_2
and map_option_4
fail.
I am very surprised that map_option_2
doesn't work, but the actual map_option
function works. To me, these are practically the same function.
Why do each of these map_option_X
functions fail to compile??
Upvotes: 4
Views: 543
Reputation: 65782
I thought that I might need to
move
the closure, so that it gets ownership ofa2b
That's correct, you do need the move
on the outer closure. Without move
, the closure would capture a2b
by reference. However, a2b
is a local parameter, and returning a closure which has a reference to a local is invalid.
Adding move
to the inner closure leads to an error because the function returns an Fn
closure. For this argument, let's consider this map_option_5
function:
pub fn map_option_5<A: 'static, B: 'static> (a2b: Box<Fn(A) -> B>) -> Box<Fn(Option<A>) -> Option<B>> {
Box::new(move |opt_a: Option<A>| {
opt_a.map(move |a| a2b(a))
})
}
If the inner closure captures a2b
by value and the outer closure is also a move
closure, then both closures end up capturing a2b
by value. By the rules of ownership, only one of the closures can possibly own a2b
at a time, so when the outer closure is called, it moves a2b
out of itself (destructuring the outer closure) and into the inner closure (which is only possible for FnOnce
closures, as they take self
rather than &mut self
or &self
). The reason for the error message is that we are returning an Fn
closure, not an FnOnce
closure. We could indeed fix this by returning an FnOnce
closure (but then it couldn't be called more than once):
pub fn map_option_5a<A: 'static, B: 'static> (a2b: Box<Fn(A) -> B>) -> Box<FnOnce(Option<A>) -> Option<B>> {
Box::new(move |opt_a: Option<A>| {
opt_a.map(move |a| a2b(a))
})
}
Now, let's discuss why map_option
works while map_option_2
doesn't. The problem stems from the fact that Option::map
takes ownership of the closure argument. Thus, we end up in a situation similar to map_option_5
above. Option::map
takes an FnOnce
, because it only needs to call it at most once. Changing a2b
to be a Box<FnOnce(A) -> B>
wouldn't help though, because it can actually be used in many calls to map
.
There's a way to avoid the inner closure: pass a reference to a2b
to map
. This works because
Box<F> where F: Fn<A>
implements Fn<A>
and&F where F: Fn<A>
implements FnOnce<A>
(and also Fn<A>
, though that's irrelevant here).pub fn map_option_2a<A: 'static, B: 'static> (a2b: Box<Fn(A) -> B>) -> Box<Fn(Option<A>) -> Option<B>> {
Box::new(move |opt_a: Option<A>| {
opt_a.map(&a2b)
})
}
The closure still takes ownership of a2b
, but it doesn't consume it when it's called, so the closure can be called multiple times.
map_option
works because its outer closure doesn't need to consume a2b
. The inner closure captures a2b
by reference from the outer closure. This works because calling an Fn
closure only needs a shared reference to the closure.
Upvotes: 7