I’m not in favour of this RFC (also, I don’t really like the diagrams with named blocks), because it mixes two different meanings of the word “lifetime”:
- The lifetime of a variable. (This meaning is applicable to visualising as a named block)
- The “lifetime” as a parameter of a reference. I think that the better term for that meaning would be borrow context (this term also don’t have an ambiguity whether “lifetime” in “reference’s lifetime” means the lifetime of a variable or lifetime of the reference (see comment above by @withoutboats) (imho correct answer is neither)).
@vi0 in RFC’s drawbacks lists “Possible confusion with generic lifetime parameters which are also have this style '-letter”, but i fail to understand how a parameter of a reference differs from a generic lifetime. And if it would reall differ, mixing notation for them would be really confusing.
I want to say that mixing those two meaning can help developing incorrect mental model – which indeed happened in my case. I’ve used Rust with that incorrect mental model for more than a year, and after I understood how it all works, I had to unlearn parts of the previous knowlege.
The (wrong) mental model I’ve had was based on “the lifetime parameters are lifetimes of variables”, and this conclusion can be easily made by looking at such diagrams as proposed in the RFC. This model would be correct for Cyclone-like region-based lifetimes (which inspired Rust), but Rust lifetimes are a totally different beast (that’s why I prefer to call them borrow contexts).
I’d like to show a few really simple examples on how the proposed notation can be misleading:
'a {
let (x, mut y) = (1, 2);
let xref: &'a i32 = &x;
y = 3;
println!("{}", x);
}
(Let’s not think about whether 'a block should cover only the reference’s lifetime or not). The example above totally fails to explain why the 'a parameter on a xref doesn’t prevent to modify y, but prevents to modify x. But you could hand-wave it away by saying "well, xref is a reference to an integer, and Rust borrows it from x, not y". But consider this:
fn foo<'a>(_: &'a mut i32, _: &'a i32) -> &'a ()
let mut x = 1;
let y = 2;
let z = 3;
let lock: '? () = foo(&mut x, &y);
// here
After the lock is stored in a variable, we can’t do anything with x, can’t modify y and can do anything with z, despite the lock being a reference to just ()! If there’s something you can put in place of '? it would be a full sentece: "x is borrowed mutably and y immutably, and their lifetimes end here and there". This is what I call borrow context. (Note that this description also fits the parameter 'a in this particular invocation of foo).
As you see, using named blocks as parameters for references works only for simple examples, and the only connection between the two meanings of “lifetime” is that every variable in reference’s borrow context has to be alive when the reference is.
Maybe these kind of diagrams actually help beginners understand borrowing (although I think that saying “x stops to exists ⟨here⟩” would be enough), but adding them to the language would be harmful. Moreover, I’d say that anywere such a diagram appears, it should be noted that it’s not only not a legal Rust code, but also only a mental shortcut. While Rust prevents misuing lifetimes even with a wrong mental model, deep understanding of lifetimes/borrow contexts is really important for writing safe abstractions for unsafe code.