piter76
February 21, 2021, 11:51am
1
How am I supposed to understand it?
for<'a>
Thank you
alice
February 21, 2021, 12:12pm
2
This means "for all lifetimes 'a". For example consider this:
fn foo<T>(var: T)
where
for<'a> T: SomeTrait<'a>,
{
...
}
this means something like:
fn foo<T>(var: T)
where
T: SomeTrait<'lifetime1>,
T: SomeTrait<'lifetime2>,
T: SomeTrait<'lifetime3>,
T: SomeTrait<'lifetime4>,
T: SomeTrait<'lifetime5>,
T: SomeTrait<'lifetime5>,
T: SomeTrait<'lifetime6>,
T: SomeTrait<'lifetime7>,
T: SomeTrait<'lifetime8>,
...
{
...
}
where the list is infinite, going through every possible lifetime.
kornel
February 21, 2021, 12:34pm
3
Less formally, it's useful when you don't care what lifetime something has (it doesn't have to match anything else specific), and you need to invent one out of thin air.
It's mostly useful for callback functions. If you used Fn(&'a T) with 'a from some outside scope, it would require the argument come from this very specific outside scope. With for<'a> Fn(&'a T) you can call it with any temporary lifetime.
piter76
February 21, 2021, 4:36pm
4
Thanks for the informal explanation. Really appreciate it.
For reference, this use of for is called a "higher-rank trait bound", and the Rustonomicon has a chapter about it.
Yandros
February 24, 2021, 12:23pm
7
There is a whole section about it in the drop-down menu of the following documentation :
https://docs.rs/stackbox/0.1.0/stackbox/macro.custom_dyn.html:
▼ What are higher-order lifetimes?
Consider the following example: you are able to create some local value within
a function, and want to call some provided callback on a borrow of it.
fn with_nested_refcell (
nested: &'_ RefCell<RefCell<str>>,
f: fn(&'_ str),
)
{
f(&*nested.borrow().borrow())
}
The main question then is:
what is the lifetime of that borrow?
If we try to unsugar each elided lifetime ('_) with a lifetime parameter
on the function, the code no longer compiles:
fn with_nested_refcell<'nested, 'arg> (
nested: &'nested RefCell<RefCell<str>>,
f: fn(&'arg str),
)
{
f(&*nested.borrow().borrow())
}
Error message
error[E0716]: temporary value dropped while borrowed
--> src/lib.rs:8:9
|
3 | fn with_nested_refcell<'nested, 'arg> (
| ---- lifetime `'arg` defined here
...
8 | f(&*nested.borrow().borrow())
| ^^^^^^^^^^^^^^^---------
| |
| creates a temporary which is freed while still in use
| argument requires that borrow lasts for `'arg`
9 | }
| - temporary value is freed at the end of this statement
error[E0716]: temporary value dropped while borrowed
--> src/lib.rs:8:9
|
3 | fn with_nested_refcell<'nested, 'arg> (
| ---- lifetime `'arg` defined here
...
8 | f(&*nested.borrow().borrow())
| ----^^^^^^^^^^^^^^^^^^^^^^^^-
| | |
| | creates a temporary which is freed while still in use
| argument requires that borrow lasts for `'arg`
9 | }
| - temporary value is freed at the end of this statement
So Rust considers that generic lifetime parameters represent
lifetimes that span beyond the end of a function's body (c.f. the
previous error message).
Or in other words, since generic parameters, including generic lifetime
parameters, are chosen by the caller , and since the body of a callee
is opaque to the caller, it is impossible for the caller to provide /
choose a lifetime that is able to match an internal scope.
Because of that, in order for a callback to be able to use a borrow with
some existential but unnameable lifetime, only one solution remains:
That the caller provide a callback able to handle all the lifetimes /
any lifetime possible .
Imaginary syntax:
fn with_nested_refcell<'nested> (
nested: &'nested RefCell<RefCell<str>>,
f: fn<'arg>(&'arg str),
)
{
f(&*nested.borrow().borrow())
}
the difference then being:
- fn with_nested_refcell<'nested, 'arg> (
+ fn with_nested_refcell<'nested> (
nested: &'nested RefCell<RefCell<str>>,
- f: fn(&'arg str),
+ f: fn<'arg>(&'arg str),
)
This is actually a real property with real syntax that can be expressed in
Rust, and thus that Rust can check, and is precisely the mechanism that enables
to have closures (and other objects involving custom traits with lifetimes,
such as Deserialize<'de> ) operate on
borrow over callee locals.
The real syntax for this, is:
fn with_nested_refcell<'nested> (
nested: &'nested RefCell<RefCell<str>>,
f: for<'arg> fn(&'arg str), // ≈ fn<'arg> (&'arg str),
)
{
f(&*nested.borrow().borrow())
}
For function pointer types (fn):
for<'lifetimes...> fn(args...) -> Ret
For trait bounds:
for<'lifetimes...>
Type : Bounds
,
// e.g.,
for<'arg>
F : Fn(&'arg str)
,
or:
Type : for<'lifetimes...> Bounds
// e.g.,
F : for<'arg> Fn(&'arg str)
Back to the ::serde::Deserialize example, it can be interesting to
observe that DeserializeOwned is defined as the following simple trait alias:
DeserializeOwned = for<'any> Deserialize<'any>
This whole thing is called Higher-Rank Trait Bounds (HRTB ) or
higher-order lifetimes.