TL,DR
"Fixed" lifetimes in argument position, can be "problematic" and universal (HRTB) lifetimes ought to be used.
Instead of:
you should use:
To see why, let's start from:
pub
struct MessageHandler;
impl<'arg> BoxFn<'arg> for MessageHandler {
type Arg = String;
fn boxit<F : 'static> (f: F) -> Box<dyn Fn(&'arg String)>
where
F : Fn(&'_ String), // for<'any> F : Fn(&'any String)
{
Box::new(move |arg| {
f(arg);
})
}
}
/// F = fn { handler }
/// for<'any> F : Fn(&'any String)
fn handler (message: &'_ String)
{
println!("Message is {}", message);
}
fn main ()
{
let boxed_fn = MessageHandler::boxit(handler); // Box<dyn Fn(&'arg String)>
let message = "Test".to_string();
boxed_fn(&message);
}
- I have taken the liberty to rename
'a
to 'arg
to make it more readable, and removed the &'static
indirection over the f: F
argument, since it is not needed (F : 'static
suffices for your signature).
So, now the question is:
what is 'arg
?
how far does 'arg
go / span?
Indeed, any lifetime parameter represents a lifetime, which can be seen as a span of the program.
Finding / choosing the exact lifetime span is Rust's job: an API bounds lifetimes, either by forcing them to reach some point, or by forbidding them from going further than some point. Rust then tries to find some "code section" / span that matches the given bounds, or triggers a compile error when it fails.
And that's what happens with your code:
-
Now, 'arg
is also the lifetime of the borrow of the String
that is fed to boxed_fn
(that's what dyn Fn(&'arg String)
means). And by definition a value cannot be borrowed beyond the point where it is dropped:
⟹ 'arg
must end before message
is dropped.
And in the code:
let boxed_fn = MessageHandler::boxit(&handler);
let message = "Test".to_string();
boxed_fn(&message);
}
it is just not possible to meet both requirements. To better see it, know that variables are dropped in reverse order of their declaration. This means that the above code is equivalent to:
let boxed_fn = MessageHandler::boxit(&handler);
let message = "Test".to_string();
¤ boxed_fn(&message);
¤
drop(message);
drop(boxed_fn);
~
~
}
As you can see, these spans do not overlap, hence the error (if they overlapped then Rust would be free to choose any point in the intersection and the code would compile). By the way, by writing down the drop
s explicitely, the error Rust shows is a little bit more explicit too:
error[E0505]: cannot move out of `message` because it is borrowed
--> src/main.rs:41:10
|
40 | boxed_fn(&message);
| -------- borrow of `message` occurs here
41 | drop(message);
| ^^^^^^^ move out of `message` occurs here
42 | drop(boxed_fn);
| -------- borrow later used here
- An example of the lifetimes overlapping can be seen if we invert the
drop
order:
let message = "Test".to_string();
let boxed_fn = MessageHandler::boxit(&handler);
¤ boxed_fn(&message);
¤ drop(boxed_fn);
¤~ // <--- 'arg can end here --+
~ drop(message);
}
The solution
By making the type of boxed_fn
not carry an explicit lifetime, while still being "callable", the problem is solved.
And a way to have a "callable with an argument borrowed for some lifetime" interface without infecting the whole Box<dyn ...>
type with that specific lifetime, is to just use a non-specific lifetime. Such a non-specific lifetime is "universally quantified" instead, meaning that the callable interface (and bound) does not apply to a specific borrow but to all possible borrows (hence the "universal").
This is what is called a HRTB, and can be written either with the explicit "for all" syntax:
Box<dyn for/*all*/<'borrow> Fn(&'borrow String)>
or, when dealing with Fn
{,Mut
,Once
} traits specifically, it can be written with the "elided lifetime parameter" syntax, i.e., writing down the '_
elided lifetime parameter (explicit elision), or just not writing it at all (implicit elision):
Box<dyn Fn(&'_ String)>
Box<dyn Fn(&String)>
So the following BoxFn
trait signature solves your problem:
pub
trait BoxFn {
type Arg;
fn boxit<F : 'static> (f: F) -> Box<dyn Fn(&'_ Self::Arg)>
where
F : Fn(&'_ Self::Arg), // for<'any> F : Fn(&'any Self::Arg),
{
Box::new(f)
}
}