Playground repro (a bit tweaked to dismiss other issues):
impl<'a> Client<'a> {
fn on<T> (
self: &'_ mut Client<'a>,
command: String,
callback: impl 'static + Fn(T) -> Option<String>, // Box<dyn 'static + Fn(T) -> Option<String>>,
)
where
T : Deserialize<'a>,
{
let handler = move |payload: String| -> Option<String> {
let payload_value: T = ::serde_json::from_str(&payload).unwrap();
callback(payload_value)
};
// To insert: Box<dyn 'static + Fn(String) -> Option<String>>
self.command_map.insert(command, Box::new(handler));
}
}
So, the thing is that payload
becomes a local of the handler
callback; the payload
is dropped when the callback returns. And, "in the grand scheme of things", one does not know when the callback may be called.
Because of this, it is not possible to name the lifetime of a borrow over payload
using generic parameters.
In your case, for the call to ::serde_json::from_str
to be valid, &payload
needs to be borrowed at least for the lifetime 'a
in order to produce a T
(since the only way to forge a T
out of a &'_ str
is through the Deserialize<'a>
bound which implies that the &'_ str
be an &'a str
). Which means that this unnameable-since-it-can-be-arbitrarily-short lifetime of the borrow payload
would need to be bigger that some fixed outer lifetime parameter 'a
. This is not possible, something that must be allowed to be arbitrarily short, i.e., infinitely short, cannot be lower bounded. Hence the error.
This reasoning may look abstract, but this is really how Rust operates, using this mathematical reasoning. There are no concrete scopes here, just the fact that &payload
, due to its "callbacky nature", must be able to support arbitrary lifetimes, even those arbitrarily short, and that outer generic lifetime parameters such as those on an impl
block or a fn
definition are, granted, unknown - parametrized, but such unknown parametrized parameter is fixed "by the time" we are reasoning about the arbitrary lifetime nature of &payload
.
Mathematically, this is a quantification problem; we are looking to solve something akin to:
∃ 'a
∀ 'payload
'payload ≥ 'a (in Rust this is written as `'payload : 'a`)
-
i.e., does there exist some parameter
'a
such that for all / for any parameter(s) 'payload
, the inequality 'payload ≥ 'a
holds.
For this to hold, the set to which these elements belong to would need to have a minimum value 'min
, and then we'd have 'a = 'min
be the solution.
But for Rust and lifetimes,
-
there is no such minimum;
-
and even if there was one (let's call it 'none
), the parameter 'a
, at the outer layer, has its own kind of universality, so we'd have to change the definition of Client
:
impl Client<'none> // or, to rename it: `impl<'a : 'none> Client<'a>`
{
... where T : Deserialize<'none>
Now, the above is not valid Rust, but there is a construct that gets very close to it: instead of using actual (outer) parameters, we can introduce universally quantified inner parameters:
impl Client<'_> // The '_ here means "we don't care about this lifetime parameter, let it be whatever it wants it to be"
// equivalent to: `impl<'dont_care> Client<'dont_care>`
// If you don't need that lifetime parameter for anything else, by the way,
// then just remove it altogether: `impl Client`
{
... where for<'any> T : Deserialize<'any>
}
which now expresses a much stronger property of T
:
-
that for all / for any lifetime(s) 'any
, it can be deserialized out of a &'any str
-
i.e., that it can be deserialized out of any borrow of a str
, no matter how short the borrow would be.
-
Playground
This is happens so often that serde
itself provides an alias for this property: DeserializeOwned
:
for<'any> T : Deserialize<'any>
⇔
T : DeserializeOwned