5

In the code snippet found in this tweet, pattern matching is used like this:

let (|x| x) = |_| Some(1); // same as `let (x | x) = |_| Some(1);`

Which threw me off. Rust's pattern syntax is defined as:

Pattern : '|'? PatternNoTopAlt ('|' PatternNoTopAlt)*
// ...

It looks like the leading '|' was intentionally allowed, why so?

4
  • If something can be gleamed from this, it looks like it's about formatting and "syntax consistency" - seems like the idea is to allow for all the options to start with a | when you have them on multiple lines. E.g. you you can interpret | Some(1) | Some(2) as "Opt1: matches Some(1), Opt2: matches Some(2)", whereas Some(1) | Some(2) seems to more readily communicate "Matches Some(1) or Some(2)" Nov 6, 2022 at 19:45
  • Rationale was discussed in Rust RFC 1925. For example, this matches the pattern syntax in many ML family languages, from which Rust is heavily inspired.
    – amon
    Nov 6, 2022 at 19:54
  • 1
    @amon This should be an answer. It's actually an answerable question, for once! Nov 6, 2022 at 21:17
  • @amon Yes, you should elaborate a little, but your comment seems to answer the question.
    – Peter K.
    Nov 6, 2022 at 21:24

1 Answer 1

8

Rust's optional leading | (vertical bar) in patterns was introduced in Rust RFC 1925. As the motivation section of the RFC explains:

This is taking a feature which is nice about F# and allowing it by a straightforward extension of the current rust language. After having used this in F#, it seems limiting to not even support this at the language level.

So, the author of the RFC just thought it was neat syntax. Rust draws a lot from ML languages like F#, Haskell, and OCaml, both in its syntax and semantics. In particular, Rust's enums/match/patterns features are very closely inspired by ML languages.

The motivating example is that a match pattern might have multiple cases A | B | C. There are some different ways to format this across multiple lines. For example, Rust's normal style:

match value {
  A
  | B
  | C => todo!(),
  _ => unimplemented(),
}

But some people think the different alignment of A and B is confusing there. Introducing a leading | helps with alignment, and could make the code more easy to read. This changes | from a pattern separator to a pattern introduction marker, just like how it is often used in the ML languages:

match value {
  | A
  | B
  | C => todo!(),
  | _ => unimplemented(),
}

The discussion for that RFC was far from unanimous, and there was strong opposition to this change. But as Josh Triplet summarized the discussion:

I'm not seeing any objections to the idea of supporting this in the grammar. I do see some comments about what our recommended style should look like, and in particular whether or not this should replace indentation of match arms. The thing that's causing those two to be conflated is that the RFC's samples all assume a particular style when writing, and not everyone is a fan of that style. As far as I can tell, every objection I've seen is objecting to the indentation style as shown in the RFC, which I wouldn't argue with.

Does anyone object to allowing the optional vert at all? If not, I would propose that we move to accept this, and then separately determine how it might affect the default style.

That is, people were mostly hung up about how Rust code should be ideally formatted, but there were little to no fundamental objections against giving authors this option to format their code more clearly.

In general, I think it's good when a programming languages gives authors the opportunity to express their ideas clearly. On the other hand, such flexibility also gives opportunity to obfuscate the code, and increases the complexity of the language as a whole – the C# language team had a concept that every feature starts with minus 100 points, but the Rust RFC process doesn't follow this philosophy.

Here, the Rust grammar facilitates potential confusion between Smalltalk/Ruby-style closures |arg| expr, patterns | pat1 | pat2, and bitwise or logical “or” operators (a | b), (a || b). The quoted tweet provides a pathological case of this for Rust, but similar obfuscation is a common pastime in other languages with complex and flexible syntax, such as Perl.

4
  • 1
    Regarding C# -100 points, one key difference in the process is rust explicit public stabilisation phase. For the C# team, at least when that was written they wrote in private, and then commited for ever. Rust it has to meet the RFC, then people actually have to use it in nightly, and if a feature isn't actually wanted and used by people then it can stick in nightly for a long time until it gets the 100 points. RFCs proposed don't guarantee stabilisation. Nov 7, 2022 at 20:25
  • Thanks! I must admit when it comes to this kind of thing my first reaction isn't to search for an RFC. It's also interesting that this RFC is relatively recent.
    – Calogyne
    Nov 7, 2022 at 20:30
  • @Calogyne So what I did to find this was to go backwards in the git-blame of the reference documentation until I found the commit that first added the optional vert in the grammar. The corresponding pull request linked to this RFC. Not sure I would call it young though. Rust is 12 years old, but got its 1.0 release only 7 years ago, and that RFC 5 years ago. This is older than the async RFC (4 years ago).
    – amon
    Nov 7, 2022 at 20:38
  • Express ideas clearly and cleanly. I like it when I can format code to make it nice and readable. This one helps. Not sure why that one comma is needed.
    – gnasher729
    Nov 9, 2022 at 17:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.