5 ms·
I wholeheartedly agree that rust’s syntax is way noisier and uglier than I’d like, and it’s nice to see someone else raise the point seriously. People tend to a
by voidhorse 4y ago
I wholeheartedly agree that rust’s syntax is way noisier and uglier than I’d like, and it’s nice to see someone else raise the point seriously. People tend to act like syntax is an ancillary detail in a language, but actually it’s fundamental! It’s our direct interface into the language itself and if it’s painful to read and write the language won’t be pleasant to use, no matter how great it’s semantics may be.
Beyond the line noise problem. I feel some of rust’s syntactic choices are confusing. For instance:
let x = 2
Introduces a new name and binds it to the value 2 while
if let Some(x) = y
Is a shorthand for pattern matching. Meanwhile other matching structures have no need of “let” at all. Likewise this extends the semantics of what “if” means and also overloads “=“ (e.g, glancing at this, would you say equals is binding a value to a pattern, performing a Boolean check, or both?) Rust has a couple of one-off weird syntactical devices that have been introduced as shorthand that imo quickly increase the cognitive load required to read code because several structures and keywords are reused in slightly different ways to mean entirely different things.
There are a lot of similar syntactic hoops around type signatures because they didn’t go with the old “type variables must be lowercase” rule which leads to subtle potential ambiguities in parsing T as a variable or proper type in some cases that thus forces additional syntax on the user.
I also think there are too many ways to express equivalent things in Rust, which again leads to more cognitive overhead. Reading the current docs, I get the sense the language is becoming “write biased”. Whenever they introduce some syntactic shortcut the justification is to save typing and eliminate small amounts of repetition, which is great in theory but now we have N ways of writing and reading the same thing which quickly makes code hard to grok efficiently imo.
This minor gripe comes with the big caveat that it remains probably the most interesting language to become vogue since Haskell.
- avgcorrection 4y agoConservative Java has something similar with try-with-resource and the upcoming instanceof pattern matching.
- irishsultan 4y ago> For instance: > let x = 2 > Introduces a new name and binds it to the value 2 while > if let Some(x) = y > Is a shorthand for pattern matching. Both introduce a new name (x) and both pattern match, it's just that the pattern in let x = 2 is simply match anything and assign it the name x, you could just as well write let t@(x, y) = (2, 4); Which binds t to (2, 4), x to 2 and y to 4 and there it's perhaps more clear that normal let is pattern matching as much as if let is pattern matching.
- burntsushi 4y agoIt might help you to think of 'if let' as an extension of 'let' rather than an extension of 'if'. That is, 'let' by itself supports irrefutable patterns. e.g., let std::ops::Range { start, end } = 5..10; So the 'if' is "just" allowing you to also write refutable patterns.
- voidhorse 4y agoThat is a useful way to think about it for sure, I’m mostly using it as an illustration of what is probably a philosophical difference between myself and the Rust maintainers; in other words, I don’t see why we need if let when we already have match with _ wildcards. It’s the sort of syntactic shortcut that gives authors of code relatively little benefit (save a few keystrokes) and readers of code yet one more syntactic variation to contend with. I guess another way of putting it is that I think Rust has a lot of sugar that’s confusing. Kotlin is an example of a language that has a lot of similar syntactic shortcuts and functional underpinnings that implements them in a more readable and consistent fashion imo.
- burntsushi 4y agoI could live without 'if let'. I'm not a huge fan of it either, although I do use it. Its most compelling benefit to me is not that it saves a few keystrokes, but that it avoids an extra indentation level. Compare (taking from a real example[1]): if let Some(quits) = args.value_of_lossy("quit") { for ch in quits.chars() { if !ch.is_ascii() { anyhow::bail!("quit bytes must be ASCII"); } // FIXME(MSRV): use the 'TryFrom<char> for u8' impl once we are // at Rust 1.59+. c = c.quit(u8::try_from(u32::from(ch)).unwrap(), true); } } with: match args.value_of_lossy("quit") { None => {} Some(quits) => { for ch in quits.chars() { if !ch.is_ascii() { anyhow::bail!("quit bytes must be ASCII"); } // FIXME(MSRV): use the 'TryFrom<char> for u8' impl once we are // at Rust 1.59+. c = c.quit(u8::try_from(u32::from(ch)).unwrap(), true); } } } The 'for' loop is indented one extra level in the latter case. With that said, I do also use 'if let' because it saves some keystrokes. Taking from another real example[2], compare: if let Some(name) = get_name(group_index) { write!(buf, "/{}", name).unwrap(); } with match get_name(group_index) { None => {} Some(name) => { write!(buf, "/{}", name).unwrap(); } } (I could use '_ => {}' instead of 'None' to save a few more.) I do find the 'if let' variant to be a bit easier to read. It's optimizing for a particular and somewhat common case, so it does of course overlap with 'match'. But I don't find this particular overlap to be too bad. It's usually pretty clear when to use one vs the other. But like I said, I could live without 'if let'. It is not a major quality of life enhancement to me. Neither will its impending extensions. i.e., 'if let pattern = foo && some_booolean_condition {'. [1]: https://github.com/BurntSushi/regex-automata/blob/fbae90682305b1c1562c5fc2fa6337cfb1d161a5/regex-cli/src/config.rs#L1533-L1542 https://github.com/BurntSushi/regex-automata/blob/fbae906823... [2]: https://github.com/BurntSushi/regex-automata/blob/fbae90682305b1c1562c5fc2fa6337cfb1d161a5/regex-cli/src/cmd/captures.rs#L307-L309 https://github.com/BurntSushi/regex-automata/blob/fbae906823...
- ntoskrnl 4y agoI'm overall a rust fan but I've always agreed with you about `if let`. What I don't like is that it reads right-to-left and starts getting awkward if either side is much longer than just a variable name. if let Some(Range { start, end }) = self.calc_range(whatever, true) { // ... } I feel it would read much smoother if you switched the two sides so execution flows left-to-right if self.calc_range(whatever, true) is Some(Range { start, end }) { // ... }
- mpawelski 4y agoAgree, this is something that I wish was changed and it's something that C# got right. I think I tried to look up why this syntax was chosen and found some old github issues when people were actually suggesting the latter syntax (pattern on the right) and I think there were some syntax ambiguities in this syntax. Not sure if this was the main reason. Maybe the lang team just didn't thought the difference is important enough (and it is for me! ;-)). C# lang designers think about IDE experience when designing language syntax (that's why we have "from", "where", "select" order in LINQ, for better IDE code completion), hope other language designers were more thoughtful about it too.
- metaltyphoon 4y agoAgreed and that' how C# does pattern matching on `if`
- beltsazar 4y ago> I feel some of rust’s syntactic choices are confusing. For instance: > let x = 2 > Introduces a new name and binds it to the value 2 while > if let Some(x) = y > Is a shorthand for pattern matching. It won't be as confusing once you realize that both do the same thing: variable binding. The difference is that the former is an irrefutable binding, whereas the latter is a refutable binding. Suppose that we have: struct Foo(i32); A few more examples of irrefutable binding: 1. As a local variable: let Foo(x) = Foo(42); 2. As a function parameter: fn bar(Foo(x): Foo) {}