[lang-team-minutes] Elision 2.0

An assortment of embarrassingly random thoughts in no particular order:

  • In the const generics thread we were floating the possibility of removing ticks, and using lifetime a as the syntax to introduce lifetimes, and just a to refer to them (much like we’d have const X: Foo resp. just X for constants). This idea is in tension with adding more ticks, in <'>.

  • Recall that the spark for adding lifetime elision in the first place was a discussion some C++ programmers (might’ve been Chrome folk?) were having on a different forum, about Rust, which got shared to one of the Rust forums, where they were essentially WTFing over Rust’s noisy and verbose explicit lifetime syntax (fn foo<'a>(a: &'a Foo) -> &'a Bar was the only option at the time). Seeing non-Rustaceans having that reaction convinced us that it was in fact an actual problem and that we should do something about it. (I actually don’t remember where this pre-RFC discussion took place, and couldn’t find it just now, does anyone else?)

    Anyway, the point I’m getting around to is that for non-Rustaceans, Foo<'> also has the risk of coming across as line noise and leading to “what is this I can’t even”-style reactions. You need to already know a lot about Rust to even be able to guess at the meaning of an unmatched apostrophe standing on its own, there.

  • I feel like our thought process here is roughly:

    1. Lifetime elision not being apparent from the function signature for user-defined types is a problem.
    2. Should we fix it? Yes. Yes we should.
    3. Okay, so what syntax should we use?
    4. *surveys available options*
    5. It seems like all of these are pretty bad, but we said we were going to solve the problem, so I guess we have to choose one of them?

    Point being that we should at least consider the possibility that the cure could be worse than the disease. If all of the options for fixing the problem would result in ghastly syntax that would end up repelling people from the language on sight, it might be less bad to resign ourselves to continue living with the problem, as we’ve been doing so far.

    (Even better, of course, would be to find a non-ghastly syntax. Provided that we can.)

  • If we allow punning the name of a variable for the name of a lifetime associated with it, as also proposed, then the elided fn foo(x: &Foo) -> Bar<'> does not have that much of an advantage over the non-elided fn foo(x: &Foo) -> Bar<'x>, any more. Relative to the “current baseline”, the elided syntax has gotten more verbose, and the non-elided syntax has gotten less so. Could we live with deprecating elision for user-defined types outright, without a direct replacement syntax, and just have people use the name-punning syntax instead?

  • (Incidentally, if I remember correctly, a couple of years ago the ability to use function parameter names as lifetime names was proposed kind of frequently, and it was always shot down with the reasoning that while it would indeed be convenient, it’s founded on a misunderstanding of how lifetimes work and would cause people to form misleading mental models. Fast forward to the present, and the lang team itself is now proposing the change. Does anyone involved happen to remember when/why/how your thinking changed?)

  • I feel like a nice thing about the lifetime elision syntax when actual references are involved is how you can just visually match up the & symbols to see what is borrowing from what: fn foo(x: &Foo, y: Blah, z: Zzz) -> HashMap<int, &Bar>. I prefer one of the syntaxes involving an & symbol for this reason, most likely Foo<&>, if the plain postfix Foo& is a non-starter due to the potential for confusion w.r.t. C++.

  • If we ever add “full” HKTs, allowing us to abstract over type constructors, and to refer to & itself as a type constructor (of kind type<lifetime, type> using my preferred kind syntax, or Lifetime -> * -> * in the Haskell notation most people are familiar with), then Foo<&> could be valid syntax, with a meaning that conflicts with the aforementioned one. This could conceivably be worked around in a number of ways, like introducing a type alias type Ref = &; and writing Foo<Ref>, or requiring & to be written as <&> like we currently do when explicitly referencing associated items (<&T>::Foo), or potentially others. (I don’t think this is a significant issue, it’s just a random thought.)

  • What if instead of decorating the types, we were to decorate the function arrow itself to indicate “borrowing is taking place across this function call”? Like, fn foo(x: &T) &-> Foo, or ->&, or something along those lines. I’m not remotely sure that I like this idea (it’s also a bit cryptic and syntaxy), just putting it out there.

  • We also have a ref keyword, which we might incidentally be phasing out with the "match ergonomics" improvements. Maybe we could use that somehow?

3 Likes