The core of the issue is that drop
is not a single function, but rather a parameterized set of functions that each drop some particular type. To satisfy a higher-ranked trait bound (hereafter hrtb), you'd need a single function that can simultaneously take references to a type with any given lifetime.
We'll use drop
as our typical example of a generic function, but all this applies more generally too. Here's the code for reference: fn drop<T>(_: T) {}
.
Conceptually, drop
is not a single function, but rather one function for every possible type T
. Any particular instance of drop
takes only arguments of a single type. This is called monomorphization. If a different T
is used with drop
, a different version of drop
is compiled. That's why you can't pass a generic function as an argument and use that function in full generality (see this question)
On the other hand, a function like fn pass(x: &i32) -> &i32 {x}
satisfies the hrtb for<'a> Fn(&'a i32) -> &'a i32
. Unlike drop
, we have a single function that simultaneously satisfies Fn(&'a i32) -> &'a i32
for every lifetime 'a
. This is reflected in how pass
can be used.
fn pass(x: &i32) -> &i32 {
x
}
fn two_uses<F>(f: F)
where
for<'a> F: Fn(&'a i32) -> &'a i32, // By the way, this can simply be written
// F: Fn(&i32) -> &i32 due to lifetime elision rules.
// That applies to your original example too.
{
{
// x has some lifetime 'a
let x = &22;
println!("{}", f(x));
// 'a ends around here
}
{
// y has some lifetime 'b
let y = &23;
println!("{}", f(y));
// 'b ends around here
}
// 'a and 'b are unrelated since they have no overlap
}
fn main() {
two_uses(pass);
}
(playground)
In the example, the lifetimes 'a
and 'b
have no relation to each other: neither completely encompasses the other. So there isn't some kind of subtyping thing going on here. A single instance of pass
is really being used with two different, unrelated lifetimes.
This is why drop
doesn't satisfy for<'a> FnOnce(&'a T)
. Any particular instance of drop
can only cover one lifetime (ignoring subtyping). If we passed drop
into two_uses
from the example above (with slight signature changes and assuming the compiler let us), it would have to choose some particular lifetime 'a
and the instance of drop
in the scope of two_uses
would be Fn(&'a i32)
for some concrete lifetime 'a
. Since the function would only apply to single lifetime 'a
, it wouldn't be possible to use it with two unrelated lifetimes.
So why does the toilet closure get a hrtb? When inferring the type for a closure, if the expected type hints that a higher-ranked trait bound is needed, the compiler will try to make one fit. In this case, it succeeds.
Issue #41078 is closely related to this and in particular, eddyb's comment here gives essentially the explanation above (though in the context of closures, rather than ordinary functions). The issue itself doesn't address the present problem though. It instead addresses what happens if you assign the toilet closure to a variable before using it (try it out!).
It's possible that the situation will change in the future, but it would require a pretty big change in how generic functions are monomorphized.