15 ms·
Rust's Ugly Syntax
- npigrounet 4y ago[dead]
- david2ndaccount 4y agoRust’s choice of sigils is aesthetically displeasing. Double colons (::) and angle bracket generics are ugly. Unmatched apostrophes is jarring to those used to them being used for strings or character literals. It was an unforced error to imitate C++, which was forced to use those characters due to backwards compatibility with C and the C preprocessor. A new language doesn’t have to use those characters. For example, the D language uses the dot as a path separator for modules, namespaces, templates, etc. as do other languages like Python.
- HideousKojima 4y agoI like angled brackets for generics but hate double colons. I assume it's because I'm coming to Rust from C# though? I wonder how C++ devs learning Rust feel.
- kibwen 4y ago> It was an unforced error to imitate C++ Rust spent enough of its weirdness budget on new semantics. I may not be a fan of C++-style syntax, but if your goal is to specifically tempt C++ developers with the promise of safer/saner language semantics, then making syntactic concessions towards familiarity with C++ is the right call. Rust isn't trying to be The One Language To Rule Them All, it's trying to target a very specific domain.
- arthurcolle 4y agoThis is almost overly permissive... Yeah sure it looks the same but the semantics are so different that the statement seems... wrong
- vlovich123 4y agoAs someone with a long history of c++ experience I found rust syntax fairly familiar even if some details are meaningfully different. For example, most code you read is very very similar. I found an easier time coming to Rust from c++ in terms of syntax familiarity than swift. I found swift much faster to write code once I actually got my head around it (I also had substantial experience with Objective-C/C++ prior to picking up swift). I think the strong counter is that you only really learn a language once, but Swift is a bridge from those coming from Objective-C not C++ and tries to hit other language goals so it’s hard to compare. Certainly this is the first real language giving c++ a run for its money in a way that nothing else has before, that’s not nothing, and it’s hard to argue whether the syntax chosen is part of that success or in spite of.
- ssokolow 4y agoBecause Rust is, in effect, a GC-less Ocaml derivative with a coat of cherry-picked C++ syntax as paint. That's why the semantics are so different. It's more of the lineage that began with ML in 1973 than of the ALGOL 68 lineage like C is. The Rust compiler was even originally written in Ocaml before it became self-hosting. That's also where syntax elements like these come from: - let for variable declaration and postfix type annotations separated with colons - 'a (Ocaml's equivalent to <T> for generics, because, on an abstract/theoretical level, Rust's lifetimes are a special type of generic parameter, right down to "a" apparently being the habitual default type variable name, just like "T" is in C++) - Option (option in Ocaml), Some, and None - match - fn (fun or function in Ocaml) - -> and => (just -> in Ocaml) for function returns and match arms - ()
- lmm 4y ago> if your goal is to specifically tempt C++ developers with the promise of safer/saner language semantics, then making syntactic concessions towards familiarity with C++ is the right call. Rust isn't trying to be The One Language To Rule Them All, it's trying to target a very specific domain. I wonder how well that's worked out for them. My impression is that there's been a lot more people moving to Rust from Python/Ruby/Javascript than from C++.
- Yoric 4y agoRecall that the initial target was browser developers. I've seen what feels like considerable adoption from among Firefox developers. Chromium developers feel very tempted, too. Feels like success!
- sho 4y ago> My impression is that there's been a lot more people moving to Rust from Python/Ruby/Javascript than from C++ Is there any reason in particular you think a lot of high-level language people are moving to Rust? That is not my impression or experience at all and I struggle to think of any reason to do so besides a large, individual change in career focus. As a higher-level language user myself, I can't think of a single reason I would ever use Rust for anything. Sure, I think it's cool, and it's always good to learn new languages, but in practise it's just not suited to my domain - and I think that goes for the vast majority of us. I would say golang has scooped off more of the dynamic language types myself, although I also consider it a productivity downgrade and/or cargo cult to mostly be ignored without compelling reason. Which is even more the case with Rust!
- j-pb 4y agoFullstack & Wasm. There comes a point in your career where you're dissatisfied with the capabilities of the warm and friendly upper layers of your stack, and you descend into the cold dark waters where things are hard but fast. I used to write primarily in highlevel languages, but if you want to write realy interesting high performance stuff in javascript, WASM+Rust is a really neat tech to use. Similarly, with WebGL/WebGPU you need some familiarity with bare metal programming. I still write a lot of JS, but I regularly drop down to Rust.
- lanza 4y ago[flagged]
- jb1991 4y agoAngle brackets aren't that uncommon, are they? Swift is a modern, elegant language that uses angle brackets for generics. Fortunately it uses the period instead of the double colon (thank goodness for that!) but I don't see anything ugly about brackets.
- ssokolow 4y agoIt's directly responsible for Rust needing the "turbofish" syntax to pin down generic return types as in let foo.iter().map(|x| x*2).collect::<Vec<_>>() If they'd used something like [T] for generics, then there'd be no collision with the < and > operators needing disambiguation in some contexts. They even have a Book of Mozilla-esque entry in their test suite named "Bastion of the Turbofish" to pin down an example of the problem case, complete with https://github.com/rust-lang/rust/blob/43192ca3d72ed0ca42ba913137585219ba0a691f/src/test/ui/parser/bastion-of-the-turbofish.rs https://github.com/rust-lang/rust/blob/43192ca3d72ed0ca42ba9...
- mvolfik 4y agoI don't think that's true - .collect[Vec[u8]]() - here you might be accessing property collect and taking the value at index denoted by the item of Vec at position denoted by the variable u8. And at the end calling the resulting thing? (That's not actually valid in rust afaik, you'd have to wrap it in parentheses like this `(...collect[])()`)
- Measter 4y agoThat is actually valid Rust, and it works exactly as you said: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=dd960239ede4419f4beeaadd35cec434 https://play.rust-lang.org/?version=stable&mode=debug&editio...
- ssokolow 4y agoThe argument that generics should have used [] generally goes hand in hand with the argument that [] for panicking indexing is a wart on Rust's "pit of success" design philosophy and that panicking indexing should have been more verbose to type than the .get() for Result-returning indexing... but probably less verbose to type than the `unsafe`, potentially UB-inducing `.get_unchecked()`.
- WalterBright 4y agoIt would have worked in C++ to use the . for them, too.
- kazinator 4y ago> Unmatched apostrophes is jarring Welcome to Lisp! 'x means (quote x) '(+ 1 2) evaluates to (+ 1 2)
- davidkunz 4y agoI don't know if it's just me, but find angle brackets really hard to read. Many times I just miss the ending one, because they have the same height as normal characters, so they don't stand out.
- zozbot234 4y agoThat's a font issue. In proper type setting, angle brackets are different from normal less/greater than characters. An IDE with good in-built parsing could use variant forms for <> characters as used in generics, as opposed to relational operators.
- davidkunz 4y agoYes, but syntax decisions of programming languages shouldn't rely on such things. I would've preferred square brackets for generics (and something else for indexing).
- arp242 4y ago«..» Always seemed like a good solution to me, from a readability perspective. But I think many will object on account of being too hard to type (which is not unreasonable; on my Linux machine it's "Compose <<" and fairly easy; I'm not sure how hard/easy it is on Windows or macOS, but even if it's easy I think most people don't know it's easy and will still make the language harder to learn).
- puffoflogic 4y ago<> are the worst characters for generics, except for all the others that have been tried. () leads to the type syntax being difficult to distinguish from value syntax, which is great in dependently-typed languages but bad elsewhere. [] is already used for slices, albeit in a different way; and C should be objection enough if you want to make types look like values. {} is a nonstarter because it leads to ambiguity between return type and function body, and in an everything-is-an-expression language, once again we are back at types-look-like-values. There are no more matched pairs left on an ANSI keyboard. Personally, while I hear you that :: is ugly, I do really like being able to distinguish module paths from value paths at a glance. And when looking for an alternative, we once again run into other characters already having far more familiar conflicting uses, or just being too weird. (E.g., # or @ could have been used, but that's probably worse than ::.)
- zozbot234 4y agoTo be fair, arrays and slices are probably uncommon enough in current code that they shouldn't get exclusive use of the [] brackets. They could just be a generic type like any other.
- cutemonster 4y agoScala uses [ ] for generics, works well. I think you're right that array access is a rare case, definitely doesn't deserve its own reserved characters. And [ ] tends to mean "crash if out of bounds", crazy to have gotten its own reserved characters :-)
- nneonneo 4y agoAnd that's why we have Unicode! Vec⟪u8⟫ Vec「u8」 Vec⟦u8⟧ Vec༼u8༽ I'm sure this could open up a whole new frontier of exciting, untypeable language designs. If it works for APL, it should work for any language!
- lifthrasiir 4y agoOr that's why we have digraphs. `Vec[\u8\]` or `Vec[.u8.]` may have worked (these examples are from Fortress [1] and Nim [2]). [1] https://homes.luddy.indiana.edu/samth/fortress-spec.pdf#page=104 https://homes.luddy.indiana.edu/samth/fortress-spec.pdf#page... [2] https://nim-lang.org/docs/manual.html#lexical-analysis-other-tokens https://nim-lang.org/docs/manual.html#lexical-analysis-other...
- rhaway84773 4y agoThe exclamation mark choice seems poor as well. It’s something one would associate with something extraordinary, like an exception. Seeing it in code all over (as a beginner…I’m sure after a while you get used to it), it screams danger to me every time I encounter it in code.
- ssokolow 4y agoIt means "DANGER: This may hide an early return or other things which violate the expectations for how control flow works."
- jeremychone 4y agoI had the same initial opinion about the '.' Vs. ':' for module separation. Why add weight to make this distinction? However, after over a year of coding Rust every day, I now really appreciate the distinction. It's an effective way to give a meaningful context point at a very low cost. I was wrong again! Which has been a consistent pattern with my initial Rust appreciations.
- 29athrowaway 4y ago> It would be much simpler to not think about error handling at all, > and let some top-level > try { } catch (...) { /* intentionally empty */ } > Much better now! NO
- jrk 4y agoI think you missed the implied wink. This post is not arguing for throwing away all performance and safety, it is showing how “ugly syntax” is actually mostly inherent complexity from trying to be safe and fast.
- ben0x539 4y agoLike, all the syntax does something that contributes to being safe and fast, but that doesn't mean there couldn't be a completely different notation for all of that. Maybe there is nothing that would make everybody happier, but for any given person unhappy with Rust syntax, there's probably a bunch of tweaks you could do to make it nicer for them. For example, in pub fn read<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> { for me it's really unpleasant that there is so much stuff between the function name and the names of the parameters. It's a bit nicer with already-a-Rust-feature where-clauses like pub fn read<P>(path: P) -> io::Result<Vec<u8>> where P: AsRef<Path> { but if nothing else I think that makes a mess of the indentation if you linebreak anywhere in that line. C++ avoids this by just yanking all the extra bits at the very start of the declaration, so we could copy that (without going all the way into C++ caricature): for<P: AsRef<Path>> pub fn read(path: P) -> io::Result<Vec<u8>> { This addresses my specific complaint, but is probably worse for other people. Who knows. As another example, the whole business with explicit Result types and the early-return question mark is basically isomorphic to checked exceptions, which may or may not be syntactically lighter/more pleasant. Would Rust enthusiasts prefer that? Probably not. Would it make the language more approachable to people coming from some Java ecosystem? Maybe? Would it be worth it? No idea.
- 29athrowaway 4y agoDo you know what are the plans to do this in Carbon?
- agentultra 4y agoIt’s still ugly! There’s nothing wrong with that. Everyone will have different opinions on these things. Bike shedding will probably never go away. The semantics are what matter (at least in most programming languages)! To me, syntax wise, I just can’t stand inline type definitions. CrabML becomes close to what I can grok syntax-wise. I spent many years with C++ and I can read TyoeScript… but to me they’re examples of bad syntax, not role models. However if you read A Logical Approach to Discrete Math or Programming in the 1990s you’ll know just how syntax matters… most programming languages can’t even reach this level of which is why I think semantics are more important.
- stephc_int13 4y agoYes that is ugly and no complaining about syntax peculiarities is not bike shedding. Code aesthetics is directly related to its readability and to programmers quality of life. Code should be made as simple to read as possible, the writing part is less important.
- MrJohz 4y agoIt seems obvious that bad syntax harms readability, in that it's very easy to construct an example of a programming language that is truly unreadable (use commas to indicate generics, everything must be upper case, backslash to call functions and pipe to separate arguments, the letter `H` is used to start a class declaration, etc - the Intercal of syntaxes). But the reverse seems more difficult to argue: that there is some ideal syntax that is objectively more reasonable than all other syntaxes. I have preferences (do/end blocks feel like unnecessary noise, I like my types to be written inline in my functions, angle brackets feel right for generics), and some of them even have a veneer of logic behind them (significant indentation is a nuisance when I want to paste a block of code that has the wrong indentation). But clearly other people have different preferences, because they keep on designing languages that don't fit my ideal aesthetics. The other side of this is that the more I write different languages, the less important syntax becomes. The places where I appreciate the syntax of a particular language are usually because the syntax fits particularly well with the language's semantics, rather than because the syntax itself is good or bad. Partial application in ML-family languages is syntactically very neat, but I don't feel the need to remove parentheses from Javascript because of it. What I really like is partial application, and using space as the function application operator works well with those semantics.
- stephc_int13 4y agoI think that many recent or a bit less recent languages uses too many symbols instead of words. Words are less ambiguous and cleaner.
- MrJohz 4y agoI mean, sure. I disagree, but I can see what you mean. But that's not an argument for one being better than the other, it's an argument of preference. You happen to like words over symbols. In my experience, when I actually start getting to grips with a new language, the syntax very quickly becomes one of the least interesting things about it.
- aniforprez 4y agoThis... reads like a joke post
- girvo 4y agoI mean the post itself basically says as much. Not quite joking, but sarcastic, and to demonstrate that (as far as the author is concerned, I have no strong opinion personally) the complaints about Rust's syntax are not actually due to the syntax, but the semantic features that necessitate some kind of syntax to make them work. It's a bit of a straw-man, of course, but it's an interesting idea that made a couple things a bit clearer for myself.
- CGamesPlay 4y agoThe C++ sample is definitely a strawman: you allowed Rust to `use std` but C++ was not permitted `using namespace std`. Still ugly, less so. And further, I think your "deoptimized" Rust sample should be the headliner, since you point out that the Rust sample sacrificed readability for performance reasons. > The next noisy element is the <P: AsRef<Path>> constraint. Should be replaced with `fn read(path: impl AsRef<Path>)` > We’d have to use some opaque Bytes type provided by the runtime. A less joke example might be to have `pub type Bytes = Vec<u8>` as a secondary declaration. I guess the rest of the post is just hyperbole. Next time, take an example that has a struct with a lifetime parameter implementing traits that have lifetime parameters. That's some ugly syntax!
- jb1991 4y agoTotal tangential but I just did a quick search to confirm my suspicion: using the word "strawman" in an HN comment these days very highly increases chances of getting downvoted. It would be curious to analyze why this is, but my guess is that it has become a cliche and suggests a lack of original perspective.
- CGamesPlay 4y agoMaybe. I was reusing the word from the article: > This is lifted straight from the standard library, so it is very much not a strawman example. And, at least to me, it’s definitely not a pretty one!
- beezlewax 4y agoBecause many people think they've won an argument by throwing about terms like that or loosely related ones. It's like a bad language framework and is often inappropriately shoehorned into conversation by people who think it makes them look smart. Reddit is atrocious for this and I suspect people don't want to see it get in the way of real discourse.
- jokoon 4y agoI prefer using a simplified subset of c++ than write rust. Meaning i just use the simple parts of c++. This rust code reads like there is a lot to learn about rust. This is a big barrier.
- scotty79 4y agoThere's a lot to learn about Rust. It's very different language from pretty much any mainstream language. Even C++. But it makes it very interesting to me. So far I've seen mostly either javesque-scriptish languages or type-algebraic-for-the-sake-of-type-algebra languages. There were some oddities like we-just-write-ast or Forth. But mostly languages seemed boring and only syntactically different. Rust is high level value semantics running on bare memory kind of thing. It's meaningfully different and useful.
- i_am_a_peasant 4y agoI've written both C++ and Rust and I've basically settled for C++ without inheritance in all my personal projects. I think Rust made me a better C++ programmer. The problem with Rust is that even when you reach a point of understanding all the base things about the language, how traits work, generics, all the reference types, patterns like interior mutability etc, I've never gotten to a point where I can get an idea and quickly implement it in Rust. That just doesn't happen. With C++ on the other hand it is much easier to prototype, and I really need that being-able-to-quickly-prototype aspect in my workflow.
- shmerl 4y agoThe point of the post basically demonstrates that all that syntax isn't just there to annoy you, but is driven by pragmatic needs to be explicit for performance purposes. But the ending "much better" isn't really addressing the actual question of whether there is a way to improve syntax while keeping all things that need to be addressed instead of dropping them one by one like the post did.
- ben0x539 4y agoI keep thinking Rust should allow you to skip the < > if there's only one generic argument. Like just say io::Result Vec u8. Much more lightweight and not ambiguous (but breaks macros people write). You can keep the angle brackets for multiple arguments like idk Vec HashMap<Option String, Result<i32, ()>>.
- zozbot234 4y agoThis would involve using whitespace as an operator. If you're going to do that (I'm not sure that it stays readable once you go beyond trivially small code snippets) I'd argue that the generic type should be postfix, as it is in the CrabML syntax. And yes, these proposed syntaxes for Rust are not practical because Rust's support for macro definitions relies on the existing syntax. But if you're willing to reimplement any macros, it becomes viable.
- ben0x539 4y agoThat's wild actually, I've been aware that ML languages exist and had a rough idea what they look like but I never realized they do postfix generics. I'm going to have to let that bake in my brain.
- lmm 4y agoThe postfix notation isn't particularly essential. Haskell has usual left-to-right application but the types are still written without extra syntactic clutter, e.g. "Vector Int".
- zozbot234 4y agoYes, left to right is more intuitive/ergonomic if multi-argument generics are common, as in both Haskell and Rust.
- WalterBright 4y agoD allows the () to be skipped for one template argument: X!(T) X!T are the same. This was Andrei's idea, and it worked out nicely.
- dgfitz 4y agoI read until I saw “loosing” instead of “losing” to describe the loss of something.
- scotty79 4y agoWhy the hell do loose and lose have the same "oo" sound? Lose sounds even longer than loose. It's asking for trouble.
- jjgreen 4y agoWhy the hell do 'bough', 'borough', 'through', 'dough', 'rough', 'cough', 'brought' all pronounce the 'ough' differently? Welcome to English!
- dagw 4y agoReminds me of a time was talking to German relative about cooking and she kept talking about making a "duff". Took me a long time to realise she was making a "dough", but had only seen the word written.
- qbrass 4y agoWriting 'o' and 'oo' to differentiate the two words was a workaround for not being able to write the letter 'z' in script, because it was easily mistaken for the letter 'ȝ'.
- estebank 4y agoAre you a simple parser? Because you read until finding the first reason to stop and bailed out. Marginally more advanced compilers will try to recover, get to the end with as much context as possible, and then complain, including maybe some extra info that came after that might be relevant to the error seen.
- dgfitz 4y agoAh shit, I’m a human not a fucking compiler. Apologies.
- vore 4y agoThe thing about the inner function is something I didn't know about and while I understand the motivation and need it definitely feels a little goofy. But if you're passing impl AsRef<Path> just to pass it as a &Path to an inner function, apart from slightly better ergonomics, there's not really any difference to just having the outer function accept &Path, no? That might the actual wart beyond just bikeshedding.
- JoshTriplett 4y agoIf you only accept `&Path`, then the caller will have to turn it into a `&Path` or you; that just moves the complexity from the one function to its many callers.
- zozbot234 4y agoIt would be more elegant to move the "inner" function to the top level, and expose a separate helper function that does conversion. The current Rust code seems to be constrained by backward compatibility, where the code was written with a generic interface to start with and this had to be preserved.
- JoshTriplett 4y agoI think it'd be most elegant if we avoid the inner/outer structure, write the function generically without calling an inner function, and then teach the compiler to automatically factor out the non-generic part of the function as an optimization.
- vore 4y agoI don’t think it’s so bad since conversion to &Path is pretty trivial. Conversely, impl AsRef<Path> can move the argument even though it only really needs a borrow, which is kind of funky (both PathBuf and &PathBuf are AsRef<Path>, after all).
- zamalek 4y agoI strongly disagree that Rust syntax is ugly, in absolute terms, even for the worst-case scenario presented in the article. Rust syntax is derived from a few very simple rules/concepts/themes. After observing how one set of operators/keywords combine, you should be able to make strong assumptions about how another set will combine. Once the syntax sinks in, it's significantly easier to read than most other mainstream languages because there are significantly fewer rules/concepts/themes to comprehend. As an extremely simple example, after learning what this does: let a = loop { break 1; }; you can bet the programmer can figure out what's going on here: let a = if true { 1 } else { 0 }; A language serves three purposes: it must be easy to write, it must be easy for a human to read, and it must be as unambiguous as possible (so that a computer can understand it). From a purely syntactical standpoint, Rust does great on all three. Most modern languages barely manage two. * Form follows function. *
- kazinator 4y agoA binary file is derived from a very simple rule: (0|1)* There: now you know everything from SVR4 UNIX executable format to H.264 video wrapped in MP4 container! Go forth and decode ...
- Donckele 4y agoOK… So what does the first line of code do? You might as well say what the second line of code does - will save me time replying again ;)
- deleted 4y ago[deleted]
- doctor_eval 4y agoI have never written a line of rust in my life but I assume that a statement that terminates with a value can be used as a rvalue in an assignment or other expression. So (I guess) both examples set the variable to 1.
- metaltyphoon 4y ago
- deleted 4y ago[deleted]
- jchw 4y ago> It would be much simpler to not think about error handling at all, and let some top-level try { } catch (...) { /* intentionally empty */ } handler deal with it: I dislike this, as even in C++, I like expected or StatusOr better than exceptions. I don't really think the Rust semantics for Result is that bad. That said, if I were to fantasize about potential improvements, one thing I'd really like is an overhaul of error types in results. I wish there was some magical way that the error type could be inferred to be a union of all of the errors that can be returned. Most of the time, needing to explicitly do this manually doesn't add much value, and worse, it leads to much less precise error types as people reuse the same union type for functions that return disjoint errors. Unfortunately, there's no obvious way to make this work out, especially since it breaks Rust's rule about non-local inference, since the return type would be inferred by the function itself. Also, it would make life a lot worse for not breaking your API. But it would make the code a lot simpler and lower effort, that's for sure. Perhaps another thing would be simply not needing to write out Ok(value), and just have an implicit conversion there, but while that could actually be done, it is also ugly for other reasons. I don't see a problem at all with the ? syntax itself.
- vore 4y agoOne thing I wish was better was block-scoped ?. I know there's https://doc.rust-lang.org/beta/unstable-book/language-features/try-blocks.html https://doc.rust-lang.org/beta/unstable-book/language-featur... so you can do e.g. let foo: Result<T, Error> = try { x()?; y()?; Ok(z) }; but it hasn't been stabilized yet. The other workaround is to make an anonymous function and invoke it immediately, but sometimes you run into janky lifetime issues with that.
- D4ckard 4y ago> I wish there was some magical way that the error type could be inferred to be a union of all of the errors that can be returned. You could use `Result<(), Box<dyn std::error::Error>` to use dynamic dispatch at runtime and allow the function to return any type that implements `std::error::Error`. Otherwise people often use the anyhow[1] or the thiserror[2] crate to easily work with errors. They allow concise error propagation and quickly creating custom error types. [1]: https://docs.rs/anyhow/latest/anyhow/ https://docs.rs/anyhow/latest/anyhow/ [2]: https://docs.rs/thiserror/latest/thiserror/ https://docs.rs/thiserror/latest/thiserror/
- gimmeAnte 4y agoThis is the rust syntax we deserve: http://antelang.org/ http://antelang.org/
- matklad 4y ago+1, ante is a treasure trove of tasteful syntactic decisions.
- ssokolow 4y agoTo be honest, I've always been unnerved by "Why is that block ending without a } or END? Is the indentation significant? Is the number of arguments for each construct back-fed into the lexer?" languages like Ante, Ocaml, and Haskell.
- valyagolev 4y agoThere's always a lot going on in every line of Rust and I respect that. But a lot of it is boilerplatey. E.g. this AsRef business - the argument is that it helps performance, but I hope that sometime in the future it would be much easier and cleaner to express things like "I want two arguments of any type I can transform to an owned string" without adding a bunch of explicit generic arguments, or even "I want a hashmap with keys and values of any type I can transform to an owned string", allowing people to use your api without adding/removing .to_owned() after strings every time they change something, or all this iter-map-to_owned business. And no, I don't mean it as having java-like semantics. Semantically such functions should stay generic over the types. There's no reason not to sugar it over, though
- oconnor663 4y ago> I want two arguments of any type I can transform to an owned string Since Rust 1.26 you can write this: fn foo(x: impl Into<String>, y: impl Into<String>) { ... } Which is almost exactly the same as this: fn foo<T: Into<String>, U: Into<String>(x: T, y: U) { ... }
- zozbot234 4y agoBut this has the same flaw as the original code in OP, where you need a separate inner function to avoid excess monomorphization. What's wrong with just taking a String (or a ref to Path) and expecting user to add .into() at the calling site?
- scottlamb 4y agoBasically you have the choice of adding a tiny bit of boilerplate in the caller, having monomorphization bloat, or adding lots of boilerplate to the callee. In general I'm with you: just stick the .into() in the caller. But this is pub std code, expected to be called from many many places, so they probably made the right choice to make it more convenient to call.
- estebank 4y ago
- deafpolygon 4y agoLet's put it this way... when you absolutely must syntax highlight code in order to be able to explain Rust syntax.. it's ugly. Let's just go with a really contrived and stupid example... "pub fn". Would it really have killed them to spell it out? Why do we need 'fn'? Functions with a return type (i.e. `fn add(a: i32, b:i32) -> i32`) is just not easy to read. Yes, I'm aware that someone here will find this 'beautifully terse and concise', as well as easy to read. But without syntax highlighting, it's just a jumble of characters to me. We read code more than we write, so clarity of code should be the goal of any 'modern' languages these days.
- JoshTriplett 4y agoBrevity isn't always a bug even when optimizing for reading rather than writing. Spending extra characters spelling out `function` doesn't necessarily make code more readable. I do, in general, think keywords should not be abbreviated. We have "return" rather than "ret", "continue" rather than "cont", and so on. This wasn't always the case; ancient (pre-1.0) versions of Rust had a lot more abbreviation in the language and library than current versions.
- geoelectric 4y agoI think there's a good reason for the function keyword to be abbreviated. We know that when people read lines of text, they pay a lot of attention to the beginning and the end and comparatively less attention to the middle. One result is that lines with common prefixes tend to blur together. To get around this, you want the reader to encounter the unique portion of the line ASAP, which makes it desirable that any kind of necessary prefix be brief. Upshot is you want the function signature to be what you see as you scan down the left side of the page, so it does make sense to keep the function keyword particularly concise.
- Gigachad 4y agoThere has to be a more objective way to rank uglyness. The example function you provided looks perfectly fine to me. I don't see how syntax highlighting makes it any better.
- lukebitts 4y agoI find caring about a language being “ugly” such a weird concept. Like, imagine caring that a hammer is pretty or not… All I care about is how well it can hammer my nails and that’s literally it.
- Torkel 4y agoI care a lot about how nice my tools are. Beauty is for sure important in all human endeavours.
- lukebitts 4y agoI care about how nice my tools are as well, but notice "nice" implies usefulness. Beauty? beauty is irrelevant, and it is irrelevant in many human endeavors where it is not treated as such.
- c54 4y agoYour interface with the hammer is your hands, so you might care if the grip is rough or slippery, apart from how well it hits nails. Your interface with code is your eyes, so it can matter a lot how it appears. We read code more than we write, so writing for legibility is important. People talk about line noise. And we use syntax highlighters and monospaced fonts. All of this is for appearances.
- lukebitts 4y agoMy interface with code is definitely not my eyes. My interface with code is the mental structures I keep in my mind while coding. Rust is perfectly legible, even better than legible, because all the structures in my mind can be simpler since the language guarantees so much on its own. Something as superficial as wether the language uses :: or whatever doesn’t even register while I’m programming. Also, that was a nonsense metaphor for you to use. The grip being slippery is so far removed from “beauty” that not even trying to change the subject to be about “interfacing” can redeem it. C++ is a slippery hammer, not because it’s ugly, but because it’s easy to hit yourself in the foot using it.
- gigel82 4y agoThe syntax is honestly what's keeping me away from Rust; it's ugly AF. I jump into an unknown C++, or TypeScript / JavaScript or C# codebase and it's just readable and easy to understand. I read a bit of complex Rust and it's making me queasy.
- WalterBright 4y agoLet's have some fun with the article's example: pub fn read<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> { fn inner(path: &Path) -> io::Result<Vec<u8>> { let mut file = File::open(path)?; let mut bytes = Vec::new(); file.read_to_end(&mut bytes)?; Ok(bytes) } inner(path.as_ref()) } In D (I'm not a Rust expert, so apologies for inevitable mistakse): public io.Result!(ubyte[]) read(P : Path)(ref P path) { io.Result!(ubyte[]) inner(ref Path path) { auto file = File.open(path); auto bytes = file.read_to_end(); return Ok(bytes); } inner(path); }
- deleted 4y ago[deleted]
- boxed 4y agoPutting the return type to the left of the function name is pretty bad. So C/C++/D loses quite a bit on that at least...
- LegionMammal978 4y agoSome nitpicks, since you're almost asking for them: 'read' taking 'ref P path' isn't entirely accurate. In Rust, 'path' is moved into the 'read' function, which destroys it after calling 'inner'. (If the caller doesn't want it to be destroyed, it provides a reference, i.e., a safe non-null immutable pointer; when 'read' destroys the reference, nothing happens.) As I understand it, 'ref P path' in D still gives the caller the ability to access 'path' after calling 'read' and the responsibility for destroying it. Also, the '?'s are important for error handling: the 'File::open()?' line basically expands to let mut file; match File::open(path) { Ok(value) => { file = value; } Err(error) => { return Err(error); } } and similar for the 'read_to_end()?', except the result is discarded. And one of the reasons (apart from flexibility) that 'read_to_end()' takes an external vector as input is so that the caller can retain the result of a partial read; otherwise, the function would have to return a vector on error as well as on success.
- WalterBright 4y ago
- zumu 4y agoRattlesnake is actually pretty good. I also like CrabML but I wouldn't expect that to be popular though. In general, I don't love Rust's choice of sigils (::, <>, etc), but the language reads well enough. That inner/outer function thing is legit the compiler model leaking into user code though.
- haskman 4y agoThey didn't explore a Haskell (or PureScript) inspired syntax, but I think it could massively improve things without compromising on semantics. The separate function signature itself is a great improvement. "Haskell flavored Rust" anyone? read :: forall p . AsRef Path p => p -> Result (Vec u8) read path = inner (as_ref path) where inner :: &Path -> Result (Vec u8) inner path = do mut file <- File.open path let mut bytes = Vec.new read_to_end file (&mut bytes) pure bytes
- brmgb 4y agoOf course, they did. The initial compiler was written in Ocaml. Early Rust looked much more like a ML language. They intentionally moved to a C-like syntax because it is more familiar to their target audience.
- haskman 4y agoI didn't mean the designers of Rust. I meant the author of the article did not consider a Haskell like syntax. I completely understand the reasons for Rust devs targeting a C like syntax. Rust had already blown its novelty budget on the borrow checker semantics.
- brmgb 4y agoCrabML is a ML-like syntax which is very close to the Miranda-based syntax Haskell uses.
- Yoric 4y agoI was actually thinking of working on something like this as a side-project in case I was laid off...
- haskman 4y agoI've been thinking about something like this for a while too. Happy to collaborate!
- oslac 4y agoThe lifetime stuff in library code gets very ugly and unreadable very fast.
- low_tech_punk 4y agoIs this a sarcasm piece from the author? The way the article ends (by throwing away more and more features from Rust) makes me wonder how sincere the beginning was.
- mlindner 4y agoI think the intent is to show first by counterexample that, no, the syntax is quite readable for what it's trying to represent. After showing that the syntax isn't the problem they then show that the language semantics are indeed what make it complicated, but you can only get away from that by throwing away the best features of Rust. Basically they're criticizing, in a roundabout way, people who criticize Rust for it's syntax by showing that such people don't actually understand Rust.
- low_tech_punk 4y agoThanks for this explanation. I finally got the point :)
- GolDDranks 4y agoIt has a sarcastic tone, but the point is to show by demonstration that the ugliness is not purely in the syntax, but because Rust actually exposes a lot of knobs to the programmer.
- verdagon 4y agoI think the reason Rust's syntax is so controversial and divisive is that its syntax works well for some areas and poorly for others. TFA highlights it well: > It is needed because Rust loves exposing physical layout of bytes in memory as an interface, specifically for cases where that brings performance. Rust (and its syntax) is designed for precision and performance and explicitness. That's great for the parts of your code that are extremely performance sensitive. However, for most of our code, we need flexibility and high-level readability a bit more... a bit of a mismatch with some of Rust's decisions. I like C#'s approach here. By default it decouples away all of these unnecessary low-level decisions, and lets you focus on what the code is actually trying to do. Then, if you want to do performance-sensitive work, you can use things like `struct`. I wish it went a bit further, but it's pretty nice.
- Yoric 4y ago> I like C#'s approach here. By default it decouples away all of these unnecessary low-level decisions, and lets you focus on what the code is actually trying to do. Then, if you want to do performance-sensitive work, you can use things like `struct`. I wish it went a bit further, but it's pretty nice. But if I read correctly what you wrote, that's not just syntactic, right?
- low_tech_punk 4y agoMy personal nightmare is Fn, FnMut, and FnOnce. I always got them in the opposite order as I intended.
- estebank 4y agoIf when trying to use them the compiler fails to provide adequate feedback to get you on track, please file diagnostics tickets! We take them seriously.
- ssokolow 4y agoI think they're saying that it's counter-intuitive that the specialization relationship between Fn, FnMut, and FnOnce is the reverse of foo, &mut foo, and &foo. I know it took me a long time for that to stick.
- conradludgate 4y agoFn: a function that can be called many times without side effects. Can be shared. Almost exactly like regular functions besides the capturing. FnMut: a function that can be called many times but can have side effects, because of this it must be exclusively held (&mut F) FnOnce: a function that can be called only once, making use of by-ownership function calls So I remember it like Fn: just a function FnMut: a function that needs &mut access FnOnce: only used once, moving ownership
- puffoflogic 4y agoI'd say that because of everything-is-an-expression, and items (e.g. functions, type definitions) allowed in block scope, rustc will accept exceptionally opaque syntax compared to other languages. But that doesn't mean that you should be writing exceptionally opaque syntax. For example, in the given example, I would demand that <T: AsRef<Path>> be written with a where clause instead. rustc will accept either, but humans should demand `where` at least whenever the traits contain `<>`.
- lmm 4y agoIt's funny how even the final example does look ugly; all those unnecessary ;s , ::s and {}s. This article has convinced me of the opposite of what it was probably trying to claim: Rust syntax is not just trying to express difficult concepts, it's genuinely bad.
- estebank 4y agoI'm absolutely biased, as one of the rustc parser maintainers, but removing all redundancies of a languages syntax is actually terrible for usability. The fewer "landmarks" the parser has to work with, the harder job tooling has to provide feedback when something was written incorrectly and increases the likelihood of two unrelated concepts being a typo away, which won't be caught by the compiler. Increased verbosity doesn't have to mean reduced readability, as long as the syntax is regular and allows you to selectively skim parts of the code you don't currently care about.
- lmm 4y agoSyntactic noise absolutely reduces readability. Redundancy may have value sometimes, but ignoring the information that's already there (newlines and indentation) in favour of a separate, parallel track isn't actually giving you more chances to catch an error, because indentation that doesn't match the blocks or newlines in the middle of an expression are generally a warning at best.
- estebank 4y agoI can give you a straightforward example: the parser can recover from missing semicolons at the end of statements. The language could do automatic semicolon insertion a-la javascript. But then that means that all the ambiguous cases have to be handled anyways, either with a confusing error, or potentially subtly incorrect behavior. The parser uses whitespace to infer intent for this, if you encounter a statement starting keyword starting a new line when you expected a semicolon, confidence is high. We also use indentation levels to infer where missing closing braces should go. That mechanism is unreliable enough that I wouldn't want to have to have to rely on that for the actual language, but for recovery you can get away with incomplete solutions. Note that relying purely on whitespace significance is particularly problematic for expression oriented grammars like Rust's, as opposed to statement oriented like Python's.
- alkonaut 4y agoI think it’s reasonable that std library code goes to one extreme of ugly-but-performant while in my own code I can choose something slightly more elegant. Such as avoiding a nested function. So while I agree with some of the points of Rust having a tendency for hiding the what in all the how (and token salad), I think looking at the stdlib isn’t always a good example of idiomatic Rust.
- kramerger 4y agoThis is a very insightful observation! The C++ template library is at times unreadable too. But I do belive that rust is somewhat ugly and more complicated than C++ - for no reason other than a bad syntax design.
- mlindner 4y agoI love this post. It's a great example of how to mock an opinion that too many have followed by utterly destroy it.
- gardaani 4y agoRust made a strange decision abbreviating keywords which are significant (pub, fn) and making things verbose where it isn't needed (separate declaration of P). Here's how my dream syntax for Rust would look like: public func read(path: AsRef<Path>) -> io.Result<Vector<u8>> { func inner(path: &Path) -> io.Result<Vector<u8>> { var file = File.open(path)?; var bytes = Vector.new(); file.readToEnd(&mut bytes)?; return Ok(bytes); } return inner(path.asRef()); } I have replaced pub with public, replaced fn with func, removed separate declaration for P, replaced :: with ., replaced Vec with Vector, replaced let mut with var, replaced snake_case with camelCase, added return keywords. Note that all these are syntax changes. Semantics hasn't been changed at all. One more thing would be to remove the semicolons.. Compare that to the original. Which one is more readable?
- throwbadubadu 4y agoYou complain about abbreviation of omnipresent keywords, but then allow function only two more letters? :D I think proofs well, syntax is overrated and it is all opinionated taste.. > Which one is more readable? Not seeing any difference.
- nneonneo 4y ago`impl` exists now, so you can write pub fn read(path: impl AsRef<Path>) -> io::Result<Vec<u8>> { in Rust without needing to declare P separately.
- GolDDranks 4y agoI like the real Rust syntax more, save for double colons. (And I might prefer other brackets to <> with generics) But how is that syntax of yours separating types and generics with traits? Currently `AsRef<Path>` looks like a type although it's not supposed to be one.
- scotty79 4y agoDo they really need to be separated? Does it matter all that much when you are creating a function whether parameter is a specific type or just sny type that implements specific trait?
- blub 4y agoThe problem is not the brackets, and the colons and so on, but rather the cultural habit of wanting to cram as many concepts as possible into as few lines as possible. A Rust function has to be generic and fast, it should absolutely use references and also Result types, some functional idiom and generally encode as much as possible in the type system. When other languages would settle for another if or an exception, Rust always has one more weird trick up its sleeve to use instead. This is the curse of the bigbrain programmer wanting to use bigbrain concepts everywhere. Crate and library developers have internalized these lessons and it has become a self-fulfilling prophecy.
- hknmtt 4y agorust is the sloth from goonies.
- awb 4y agoBuilding a language that functions well is different from designing a language that reads well. Just like building an app that functions well is different from designing an app that looks great. We don’t expect every backend developer to be the best front end designer and at some point we shouldn’t expect every language developer to be the best language designer. It might be worth treating these two tasks as separate disciplines that can be studied and practiced independently. Otherwise, we keep getting languages that feel akin to 1990’s websites. They function great, but look as if the aesthetic is an afterthought.
- brundolf 4y agoI think most of what people call Rust's "ugliness" is actually just Rust's explicitness. You have to chain a lot of method calls for things that might happen implicitly in other languages; you have to annotate lifetimes and constrain generic type params; sometimes you have to manually parameterize the generics you're using; etc. But I think most of that is unavoidable without shifting some of Rust's core values. The only piece of surface-level syntax that I'm really truly bothered by is the closure syntax: fn_with_a_callback_1(|param| param + 2); fn_with_a_callback_2(|param| { println!("{}", param); }); - The fact that the | | is the same character on each side - The lack of directionality from the args -> to the body - The lack of any kind of overlap with the normal function syntax fn foo() Especially compared with something like JavaScript which has a really lovely lambda syntax, this one's hard to look at. I'm sure there were good reasons around ambiguity with other syntaxes or something, but... eesh.
- lifthrasiir 4y agoTo my knowledge, this closure syntax was a direct Ruby influence mainly because this syntax was also supposed to work like Ruby blocks: `array.each |e| { ... }`. Originally owned and shared closures had a different syntax (`fn~() { ... }` or `fn@() { ... }`)---this was back when Rust had a built-in shared reference, roughly equivalent to `std::sync::Arc` in the modern Rust---and it was pointed out that they should be harmonized at some point [1]. It was that time when people realized that then-loop-only syntax can be generalized into any closures, and this semi-decision got stuck even after the "internal" iterator that accepts a closure has long gone. [1] https://github.com/rust-lang/rust/issues/1864 https://github.com/rust-lang/rust/issues/1864
- fsckboy 4y ago> I think most of what people call Rust's "ugliness" is actually just Rust's explicitness. I think most of what people call C's ugliness is actually just C's power. Some of it is C's age: we can't blame C for being born in the very early 1970s (when sensible ISAs were just born), but when you update C, don't try to make it be something that it's not. You see the beauty of UTF-8? People who could not have invented UTF-8 can at least see the beauty of UTF-8. Unfortunately, people who could not have invented C, are updating C. When you try to update C, not only don't wreck it, don't try to replace it with something emo, weak, and overwrought, which is what Rust is. C is powerful, natural, and concise. It doesn't wipe your ass for you. Wiping your ass neatly is not something you want to entrust to a bulletproof syntax, not when you can always just wash your hands. Rust? Rust has the color of not washing your hands because Rust imagines that it made it unnecessary.
- scotty79 4y agoPersonally I'm fine with semantics but I don't like how lifetime arguments are mixed with template arguments and how types that contain and behave like borrows have no visual indication of that in their names.
- Measter 4y ago> [...] but I don't like how lifetime arguments are mixed with template arguments [...] It does make sense if you think of lifetimes as being part of a type system. Almost all lifetimes are generic (aside from 'static), so it kind of makes sense to put them in the section for declaring generic parameters. It also works for how you constrain lifetimes: `'foo: 'bar` means that `'foo` outlives `'bar`, but could also be read as `'foo` inherits `'bar` because, like in type inheritance, a `'foo` can be used anywhere that wants a `'bar`.
- nirui 4y agoThe article only provided one small example, and the code is not really that ugly. There are many third-party projects/libs that exposes way more ugly APIs, specially those which heavily utilize generics and `where`s. These things, while small and can be improved individually, wears programmers down quickly. The syntax design of Rust is adding to the situations as well. For example, Rust is probably the only language that makes me cautious about the usage of semicolon, as you might have loads of unsound problems if you misused one. My experience with Rust has been generally great, but I think these small details really deserves some thoughts from the Rust team. BTW: I also wouldn't really trust those people who have to navigate through all the quirks of something, and then calling their experience "Great". There are people who memorized the entire `:help` book of `vim`, just so they can use the editor at hyperspeed instead of lightspeed. Those people are monsters, they can eat RAM chips and then spit out the bits cache line by cache line. You give them a grab of sand, they'll make a computer out of it and play DOOM on the computer. So of course those people well have great experience on almost everything (Or they just been polite, like me)
- thom 4y agoOne of the suggestions I liked from Uncle Bob’s Clean Code was that the contents of each method should be at a single level of abstraction. In languages that require constant bookkeeping this is either impossible or requires sacrificing performance or correctness. The level of satisfaction reported amongst Rust programmers implies this isn’t an enormous issue for them, but I don’t know if we as an industry have reliable measures of productivity and correctness. Either way, nice that Rust is able to devolve into a less optimal but simpler language in the ways shown in the article. I often feel some guilt when giving up on highly templated C++ and doing things the OO way, but it’s simpler to reason about and still plenty fast.
- sweetjuly 4y agoI would actually love an ML frontend for Rust. I've always felt it was just such a nice and legible way to write code, hope CrabML someday becomes a real thing
- kgbcia 4y agoIs it possible to generate a language that just uses reserve words. I like how Python uses "and" and "or" instead of && and ||
- heavenlyblue 4y agoI am wondering if author is the one who would use all of those nasty-but-beautiful utf special maths/greek characters which were super-popular a couple of years ago. I don't find Rust's syntax ugly. But then I don't find Modern Art ugly as opposed to what many people think so well...
- throwaway234623 4y agoIMO, Rust isn't going to scale well * horrible syntax * crates.io is a graveyard of typo-squatting and lacks quality control, and no vendors / namespaces * slow compilation * standard library doesn't have HTTP, requires third-party libraries that are still immature / suck. * widespread & prevalent abuse of .unwrap() * error handling sucks because rust lacks set-theoretic types * rust's async doesn't mesh with structured concurrency * rust's async requires third-party libraries like tokio highlights of rust * pattern matching + ADTs * move by default * affine types * concurrency & parallelism are easy * serde crate * kube crate * rayon crate
- zanellato19 4y agoRust has already scaled well.
- ssokolow 4y ago> * standard library doesn't have HTTP, requires third-party libraries that are still immature / suck. > * widespread & prevalent abuse of .unwrap() [citation needed] > * rust's async requires third-party libraries like tokio As opposed to baking Tokio in and having people lament that they can't replace the executor for stuff like embedded use-cases? I remember hearing that there's interest in using async/await inside the Linux kernel with a custom executor.
- deleted 4y ago[deleted]
- rfoo 4y agoIf we make PathRef an alias of AsRef<Path>, the following code looks much prettier to me: pub fn read(path: PathRef) -> io::Result<Vec<u8>> { fn inner(path: &Path) -> io::Result<Vec<u8>> { let mut file = File::open(path)?; let mut bytes = Vec::new(); file.read_to_end(&mut bytes)?; Ok(bytes) } inner(path.as_ref()) } I don't see noise in the remaining code, like the author said, they all have meaningful semantic (besides the repeated return type).
- yakubin 4y agoIMO The author proved here that Rust could have a much better syntax, when they showed the CrabML example.
- jeremychone 4y agoCode aesthetic appreciation is a temporal matter. Something that looks ugly initially might feel natural and sound later. This happened a lot to me while learning Rust. Rust has to be learned, true, but once the initial learning phase is done, those syntaxes start making sense and become part of the value rather than the cost. I would recommend putting personal aesthetic preferences aside while learning and reassessing them later. Also, I hope the article's point was not lost. It took me too long to get it, but I am glad I did before I wrote my first comment on Reddit a few days ago.
- deleted 4y ago[deleted]
- jeremychone 4y agoIt's interesting. The same post was on the Rust Reddit a few days ago, and almost everybody got the point of the article, which is the opposite of what we could infer from the title alone. Many have a good laugh as well. But somehow, on HN, the article seems to have been taken literally with long threads about "::" v.s. "." and how < > are bad for generics. Physics over optics, not everything is about cosmetics.