What is a good mental model of borrow checker?

Yes, you're drilling into the heart of it. Let's compare:

fn take_hr_closure< F: Fn(&   I) -> O, I,     O>(f: F) { let _ignored = f; }
fn take_closure<'a, F: Fn(&'a I) -> O, I: 'a, O>(f: F) { let _ignored = f; }

The first says that F must accept any lifetime [1] and produce the same output, O, for any lifetime. It can be called with any lifetime; the caller of take_hr_closure has no say in what lifetimes it will be called with. The lifetimes have no lower bound.

Incidentally, a desugared version would be
fn take_hr_closure<F: for<'any> Fn(&'any I) -> O, I, O>(f: F)

and F is a higher-ranked type.

The second says that F must accept one specific lifetime ('a) of the caller's choosing. The lifetime does have a lower bound: it's impossible for the caller of take_closure to choose a lifetime shorter than the call to take_closure.

These are very different things! Instead of ignoring the closures... let's use them!

fn take_hr_closure< F: Fn(&   I) -> O, I: Default,      O>(f: F) {
    let i = I::default();
    f(&i);
}

fn take_closure<'a, F: Fn(&'a I) -> O, I: Default + 'a, O>(f: F) {
    let i = I::default();
    f(&i);
}

The second does not compile because of the aforementioned lower bound. The contrast is even more stark for &mut / &'a mut.

Here's a comment that drove this home for me. It's a bit dense.


Here's an example with some brief explanation at the top. It was written before GAT stabilized, but in brief this pattern:

trait Indirect<'a> {
    type Arg: 'a + FromValue<'a>;
}
// Or sometimes
trait Indirect<'a, _Lifetime_Guard = &'a Self> {
    type Arg: 'a + FromValue<'a>;
}
// Elsewhere
where T: for<'a> Indirect<'a>

are GAT simulations of

trait Indirect {
    type Arg<'a>: FromValue<'a>;
}
// Or
trait Indirect {
    type Arg<'a>: FromValue<'a> where Self: 'a;
}
// Elsewhere
where T: Indirect

I have a cleaner playground version in my drafts somewhere.


  1. that is well-formed, i.e., cannot be outside of I's validity ↩︎