As others mentioned, the lifetime parameters are of the borrow, not of the lifetime (or scope) of the value (ie referent). When you declare a lifetime parameter, whether in an fn or a type (struct, enum, trait, etc), it’s a generic parameter - think of it like a
T: PartialEq generic type parameter with a constraint. Unlike generic type parameters, however, the caller never specifies the concrete lifetime - it’s selected by the compiler. So whereas you can call a method expecting
T: PartialEq with a type of your own choosing, so long as it satisfies the constraint, you never get to do this for lifetimes.
Instead, the lifetime constraints (eg
'b: 'a) are solved for by the compiler. So the compiler is presented a constraint problem, and it tries to solve it. The lifetime here is, to reiterate, a borrow lifetime - it is not the lifetime/scope of the value. The compiler picks the smallest borrow scope that satisfies the constraints. In this case, it can borrow a longer scoped value for a shorter borrow and still meet the constraints you’ve set out. This is similar to how you can use a single lifetime parameter 'a there for both arguments. It doesn’t mean that the values live for the same amount of time - it’s a relationship that you’re expressing (taking something with 'a lifetime, and returning something with at least 'a lifetime).
In fact, you cannot use lifetime relationships (ie outlives constraints) to require that one value lives longer than another - this is not supported in any way by the Rust type system.
The typical case of needing outlives constraints is when you have mutable borrows over invariant data - in that case, compiler does not allow shrinking lifetimes like in this case. That’s to prevent a longer lived type from being substituted for a shorter requirement, but then having a shorter lived reference given to it that leads to a dangling reference - the linked nomicon page goes into more detail on this so I won’t repeat it here. So when lifetimes can’t be shrunk down but you still want to modify some invariant type, you’ll need to provide the additional outlives constraints so the compiler will know that you’re not in danger of creating a dangling reference, and it will then check that when it substitutes concrete lifetimes for the generic ones.
Let me know if something’s unclear and I’ll try to elaborate. This is not an easy thing to explain (or understand) once you get into the nitty gritty. The whole thing is predicated on “can memory safety be violated otherwise” - how you describe/relate lifetimes is then based on that.