Lifetime notation

It has long been argued that the lifetime notation is unnecessarily noisy. I’m just going to leave you with a simple change that, I think, do not introduce any ambiguities:

Current:

fn foo<'a, 'b, T>(&'a mut self, bar: &'b T) -> &'b T

Proposed:

fn foo<a&, b&, T>(a& mut self, bar: b& T) -> b& T

It eliminates the harrowing apostrophe but keeps the & next to the name in type parameter lists, hence retains ability to highlight. Friendly with editors, less intimidating to programmers from C-style languages, shorter, one less sigil, etc.

2 Likes

Having a space between the & and the T would still confusing highlighters, no?

(I mean, existing ones for C like languages, if you have to special-code the highlighter, then it’s not an improvement, just the same old thing)

Hmm? The space is not strictly necessary if you want them conjoined, but you could match either or both with a simple regex, I suppose.

From a highlighting perspective this is an improvement to just removing the apostrophes (which has been previously discussed), as you can use the ampersand to match named lifetimes.

What’s really motivating me to suggest this is getting rid of the apostrophe, which is such a syntactical thorn.

I used to hate the current lifetime syntax, but the more I see alternative proposals, the more I like it.

The noisiness is not a huge problem anymore since lifetime elision.

Personally I have no problem with the apostrophe, it make obvious I am facing a lifetime so it is great for readability. The proposed syntax feels far less readable to me just to save one character.

5 Likes

It’s not just to save a character. Other reasons include editor problems with unmatched apostrophes and unnatural syntax for potential adopters without a background in ML. It would also be nice to continue the general trend of easing the sigil burden of the syntax in rust.

Even though the elision rules remove some of the need to spell out each lifetime they certainly don’t apply everywhere. Lifetimes are a core feature in rust. I think we should do what we can to make that syntax less intimidating.

The proposal makes it quite a bit harder to read and leads to even more confusion. & already has meaning, conflating that meaning doesn’t exactly help. Not to mention & is quite understood across languages.

3 Likes

The lifetime syntax was definitely completely new to me, but the word “unnatural” never occurred to me (except maybe the concept itself). The most confusing parts for me were figuring out where and when to use lifetimes; the apostrophe is possibly the least intimidating part. And it helps the lifetimes stand out when glancing over the code.

I absolutely do agree that confusing editors/IDEs is a big downside.

2 Likes

The name left of the & would be the associated lifetime of the reference. No meaning is changed or conflated.

I admit, the look is a bit more dense, reducing the characters required. I never thought of the apostrophe as a characteristic so much desired. I have no more arguments in defence.

The interaction with lifetime bounds on generics seems a bit odd. Like

impl <T: 'a> Bar<T> { ... } becomes impl <T: a&> Bar<T> { ... }, I guess?

Or would you drop the & there?

I’d suggest using the a& form everywhere, for uniformity.

My hope is that one would learn to immediately see the prepending identifier as the name of a lifetime when seen together with an ampersand.

They will be no problem with Rust aware editors. If your editor does not handle Rust, you’d better use no highlighting at all. There will be a lot of problems if you use a highlighter from another language (litterals, closure, …).

In my opinion, it’s a good point if the apostrophe seem unnatural to newcomer, since it introduce an unnatural concept for them. When they see it they understand they are facing something new. If they see something before and after a “&”, they would probably expect some kind of “and” relation between them and they will have no clue about what the lifetime part mean anyway. It seem even more confusing to me.

Lifetime elision is good to make the syntax less intimidating since it remove completely the lifetime notation in most of the cases. I don’t think removing the apostrophe will make the syntax less intimidating since the lifetime is still explicitly named and more difficult to identify.

2 Likes

I’m not a huge fan of symbols with contextually sensitive double-meanings. For me it’s bad enough that lifetimes get put in the <> with generic type parameters. Yes lifetimes are part of the compile-time type information, but I feel like they need to be more distinguished, not less so.

As someone already said: in most cases in (current) Rust lifetimes are elided. I would go further and say in the cases where they aren’t the syntax should form a glowing neon sign around them saying “HERE BE DRAGONS”. Apostrophes serve this purpose at the moment, if only because they look a little ungainly/lonely when unmatched.

If the goal is to get around the awkward syntax highlighting of unmatched apostrophes, why not use an unused symbol like ~ or #. Overall I think any change aimed at making lifetimes more readable or usable is probably going to have to go further than just swapping out '.

2 Likes

As someone relatively new to Rust (potentially representative of new users in the future) I like the current syntax as it makes it obvious that you’re dealing with the lifetime of some reference as opposed to the reference/trait/generic itself.

It is a little bit of a quirky notation but I wouldn’t say that’s a bad thing as it clearly delineates concepts. Given as well that it’s something fairly unique to Rust (afaik), quirky notation is probably advisable in order to highlight the concept.

2 Likes

The unmatched single quotes are extremely unnatural for people coming from a C/C++ background (our primary target audience). I’ve heard this complaint from some of my smarter and more knowledgeable friends (who are also familiar with Haskell and other languages, and not just living in a C-family cocoon). For that matter, I share it myself. The rest of Rust’s syntax does a really fantastic job of fitting new concepts into a familiar C-style syntax in an intuitive way; it’s only 'lifetimes which stick out like a sore thumb and break the metaphor.

I don’t really agree with the “it’s good that the syntax is weird, because lifetimes are weird” argument. If we go out of our way to make something seem intimidating, that does not make it easier to learn. This is kind of similar to the "unsafe code should be verbose and scary, to discourage people from writing it" thing, and how people tend to perceive “monads” as an alien concept from outer space because of the way they’re usually presented.

I do agree very much that the syntax should be clear about what it means, as far as possible, and not try to sweep things under the rug. But that doesn’t mean it necessarily has to be strange or scary.

My preference would be to drop the current ticks everywhere a lifetime is mentioned, and instead use an explicit lifetime or scope keyword (whichever way that decision goes) where a lifetime variable is introduced. So where we currently write:

fn get_bar<'a>(foo: &'a Foo) -> &'a Bar;

that would instead be:

fn get_bar<lifetime a>(foo: &a Foo) -> &a Bar;

or:

fn get_bar<scope a>(foo: &a Foo) -> &a Bar;

Manually written lifetimes are going to be infrequent due to elision, so it doesn’t hurt to be explicit (and likely helps for comprehension). For user-defined types with lifetimes it would go likewise, e.g.:

fn my_get_bar<lifetime a>(foo: MyRef<a, Foo>) -> MyRef<a, Bar>;

We could also tweak the syntax of references to use {}, which would be a bit noiser but also perhaps more obvious and suggestive ("this reference is valid in the block a"):

fn get_bar<scope a>(foo: &{a} Foo) -> &{a} Bar;

fn get_bar_mut<lifetime a>(foo: &mut{a} Foo) -> &mut{a} Bar;

This possibility was floated back when lifetimes were changed from the previous / syntax to the current '; many people preferred it, but it was blocked by a syntax ambiguity. I’m not sure whether we would still have that issue. (It might’ve been something to do with struct literals, whose grammar has seen some improvements since then.)

(And in case it’s not obvious, I was alternating between scope and lifetime in the above only because I didn’t want to want to write everything twice; we would use whichever word we decide on.)

3 Likes

I like the existing syntax as-is. When I first encountered it, it was very clear to me that there was something new going on that I had to learn about. Something like a& blends in so much, and is easy to miss (it looks like a reference of type a from c/c++, or something along those lines).

I guess another way to put it: the existing syntax looks like it represents a distinct orthogonal concept, which it does. To me this makes it cleaner, not messier.

2 Likes

Just to add two points:

Besides code editors, there are tools which do generic syntax processing, e.g. Stack Overflow syntax coloring and presentation tools that insert ‘smart quotes.’ A keyword or a different symbol would fix that.

A keyword like lifetime is more self-teaching and more obvious how to search for more info.

1 Like

Great point about colouring on websites.

The unmatched apostrophe is seldom supported. I’ve seen cases where the background colour is changed to red by the highlighter to indicate a missing closing apostrophe. It’s not beautiful. The whole thing looks like a blaring syntax error.

A keyword is kind of nice but does not help a highlighter. Lifetime names would be indistinguishable from other lowercase identifiers.

Having the lifetime name be immediately followed by its ampersand, on the other hand, would be a distinct feature to look for, immediately recognisable by both programmers and highlighters.

The same form would be used in all positions – not sometimes with a keyword and sometimes without.

A reference without the name on the left would simply be known as a reference without a named lifetime, or with an unnamed/unspecified lifetime.

C++ recently introduced this: 2`572`340. I don't think unmatched ' is going to surprise many. It's a new concept, it gets a new syntax. Particular character does not matter that much.

Nobody who ever saw Haskell can be surprised by unmatched apostrophes, because in Haskell they are actually allowed in identifiers. And commonly used in them, too.

Now this is some unnatural syntax, because it has some parameters introduced by keyword and others not. That is strange.

And this is even more unnatural syntax, because it strings two identifiers together without any operator between them and without indication of what is what. It will be read as “reference to ‘a’…WAT?”. Especially since the declaration of ‘a’ as lifetime is quite some way back.

Using different sigil for lifetimes, may be. But using sigil for them is reasonable. The are a special thing.

1 Like