Lifetime operator as enclosing operator

Syntax weirdness is a very subjective thing. I, for the most part, really like Rust's syntax, even though it uses unusual symbols.

On the contrary, I find it intuitive.

  • T: Clone is a trait bound. It expresses that T has the capability of being cloned.
  • T: Clone + Default is a pair of trait bounds. It expresses that T has the capability of having a default value in addition to the capability of being cloned.
  • T: 'a expresses the capability of borrowing T for 'a, by requiring T to live at least as long as 'a -- meaning that to pass in the concrete type A<'b, B<C, D<'c>>, &'d i32> to T, it generates the constraints 'b: 'a, 'c: 'a and 'd: a.
  • T: Clone + 'a is just the way to require both a trait capability and a liveness capability.

Consider you options before starting to do that:

  • Use the Rust language longer, with the pure syntax. It's likely you're going to get used to the syntax. A year ago I started learning C#, and I some aspects of the syntax pretty unusual. But just 1 year later it looks completely normal to me.
  • Make your tool. You are free to create such a tool, but bear in mind:
    • It's going to be a lot of work.
    • Your syntax preference might not match others'.
    • Rust code other people write will still be written in original Rust syntax -- unless you make your tool work both ways, you still have to read Rust code.
  • Find a language better suited to you. I would be sorry to see you go, because I really like the Rust language, but it might not be for everyone. I don't think it's worth quitting over only syntax preferences, but who am I to tell.
6 Likes

My problems with this are, that the add operator is used in other ways otherwhere and I expect a list of bounds, not a sum.

Also it mixes different types of syntax for capabilities. Clone is an identifier, 'a is also an identifier, but follows a different syntax in usage and definition. Also the lifetime is local abstraction, while traits are a global abstraction.

I'm planning that. Though actually I don't want to get used to some syntax, which I consider bad habit.

Well, kinda not really. Even though the wg-grammar project is still in progress, there do exist (hopefully) usable grammar definitions of the Rust language, which can be modified.

I know. That's why I'm planning to kinda extend the syntax instead of replacing it. Current Rust syntax should still be usable as is. Nevertheless I'm also thinking of writing some standardization, which people can stick to or not.

Maybe I could write a tool for that too.

Nevertheless specifying and using a custom extension of the language only makes sense if others use it to. I kinda hope that others also see all the flaws with readability, maintainability and usability of the current syntax.

Sure I could stick to C++. But Rust got features C++ does not offer, and I've waited long enough for a new good language to come up, but as this did not happen it seems like now I have to get along with Rust.

This topic comes up a lot, and in case you weren't are of it, I want to share a syntax proposal that I think is quite nice for short parameter names:

Instead of fn foo<'a, 'b, T>(&'a mut self, bar: &'b T) -> &'b T, it's proposed to write fn foo<T>(&mut self, bar: &T) -> &'bar T, that is, for every reference parameter, a lifetime parameter with the same name is introduced. This idea just has the flaw that the different lifetimes of &'a &'b Foo can't be named.

Yeah, I also thought of something like that, but as I haven't yet explored the full language, so I didn't yet think deep into it. But I saw the problem, that I got no idea, how to name my lifetimes. It should be a speaking name.

Lifetimes are an underlying conept for variables. Lifetime operator serves the purpose to relate lifetime of variable to each other. So the definition of a lifetime relation should include the names of the related lifetimes.

Generally Rust lifetimes are not given speaking names. 90% of Rust code uses 'a, 'b or, whenever possible, elides them completely.

I don't know whether this is a good thing or not, but that seems to be the status quo. As for the reason, there are multiple possibilities:

  • They are local: lifetime parameters are always local to the current struct or fn definition or impl block. This makes it easier to find their definition and the other places they are used, which are ulimately what determine the "meaning" of the parameters. This makes the names less important.

  • The shorter the less distracting: the shorter the name of the lifetime parameter, the less visual space it takes up in definitions, which makes patterns easier to recognize at a glance. There is a tradeoff there.

    Generic type parameters also mostly use single-letter names, and this might be the biggest reason for this.

  • Often it is indeed hard to come up with good names for them: having a lazy way of just slapping 'a and 'b on them sidesteps the issue. There is still the possibility to name them after the field(s) or parameter(s) they are used in, if that makes the code more readable.

So I find that the status quo is fine, but it's possible that there is a better practice of naming lifetime parameters out there.

(What are all these bulleted lists?! What am I, ChatGPT?)

Comparing to generics, I'm often enough also using T, U and V. Sometimes, when you have special meaning of the generic, you might use other Letters, like F for a function or R for a return type. Nevertheless, speaking names often would be possible and sensefull. Even a "Value" is better than "V" in terms of readability and clearity (and even more for people who are new to the language).

I guess what makes it harder for lifetimes is, that they don't represent a type or anything for themself, but resemble a connection of other elements.

I would argue that using Value instead of V makes it less readable, because now you no longer have a clear indication of which type is generic and which is concrete.

Also note that this convention is not exclusive to Rust, so it might be intuitive or even expected to some people that are new to the language.

You might be right for one argument, but the more generics you have, then more confusing non-speaking names are in the context.

See the following (C++) example:

template <size_t i = 0, class F, class U, class... V>
constexpr U fold(F&& op, U&& init, V&&... ts){
//...
}

vs.

template <size_t index = 0, class Op, class Init, class... Tuples>
constexpr Init fold(Op&& op, Init&& init, Tuples&&... ts){
//...
}

Already the signature gets less clear, the meaning of the generic types only gets clear indirectly by the parameter names (and even then it's kinda vague). The clearity of the rest of the code isn't much better.