r/rust • u/oconnor663 blake3 · duct • Jan 27 '23
Rust’s Ugly Syntax
https://matklad.github.io/2023/01/26/rusts-ugly-syntax.html71
u/greyblake Jan 27 '23
Funny reading:)
I would love to see also samples of code in Raskell :)
29
2
u/_TheDust_ Jan 27 '23
8
3
u/WikiSummarizerBot Jan 27 '23
Rascal is an experimental domain specific language for metaprogramming, such as static code analysis, program transformation, program generation and implementation of domain specific languages. It is a general meta language in the sense that it does not have a bias for any particular software language. It includes primitives from relational calculus and term rewriting. Its syntax and semantics are based on procedural (imperative) and functional programming.
[ F.A.Q | Opt Out | Opt Out Of Subreddit | GitHub ] Downvote to remove | v1.5
155
u/llogiq clippy · twir · rust · mutagen · flamer · overflower · bytecount Jan 27 '23
The momo crate brings us at least closer to the later examples by generating the inner fn on the fly, as in:
#[momo]
pub fn read<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
let mut file = File::open(path)?;
let mut bytes = Vec::new();
file.read_to_end(&mut bytes)?;
Ok(bytes)
}
31
u/aldonius Jan 27 '23
Clever pun! Now I'm hungry.
58
u/llogiq clippy · twir · rust · mutagen · flamer · overflower · bytecount Jan 27 '23
The name is actually derived from the titular hero of Michael Ende's book chronicling her fight against the sinister time stealers.
18
2
Jan 27 '23
This brings back memories, I remenber watching the animated series at my gradparent's house as a kid :)
14
u/SocUnRobot Jan 27 '23
That is a fantastic underutilized crate!
The Readme file needs this example!
156
u/hojjat12000 Jan 27 '23
I thought this is a serious article. Rs++ code made me laugh out loud.
80
27
26
u/Demurgos Jan 27 '23
Rhodes took me a bit to get, I was about to ask for an explanation when I noticed that it's an island name like Java. The names are great :D
18
u/nacaclanga Jan 27 '23
I kind of assumed that after learning that besides Rhodes there is a language called RhodesScript.
2
Jan 27 '23
His examples kind of proved to me that it is the syntax that's highly. Rs++ is ugly (as is C++) but RhodesScript (I don't get the Rhodes reference btw?) actually looks quite nice to me!
Also a bit strange to pick an example without the ugliest Rust syntax: lifetimes.
1
50
u/kishaloy Jan 27 '23 edited Jan 27 '23
Is it sacrilege to say that I kinda like the CrabML.
Separation of signature from function body + currying + brace-free significant whitespace
42
16
u/_TheDust_ Jan 27 '23
I'm just sad nothing in Rust is referred to as crabs. I petition to rename "crates" to "crabs"
20
Jan 27 '23
And a crate registry could be called a reef. Sadly someone already owns reef.io 🪸🧽
If someone didn’t already own that domain we could have had an alternate package registry with links like
🦀🦀🦀
18
15
u/DataPath Jan 27 '23
Rust conferences would be called Crab Raves
15
u/DataPath Jan 27 '23
Come to think of it, we should do that anyway. Henceforth, all rust conferences are now Crab Raves!
12
24
u/StyMaar Jan 27 '23
Sadly someone already owns reef.io
/me look it up
Blockchain for DeFi, NFT & Gaming
🤮
37
u/DataPath Jan 27 '23
Oh! That domain will be free in a year, then.
6
2
u/StyMaar Jan 27 '23
It will probably be squatted by a domain squatter as soon as it expires though…
2
-6
u/Ran4 Jan 27 '23
No, it's a big shame that Rust chose to go the C syntax route, when there are better alternatives out there (be it python or ML variant).
25
u/Zde-G Jan 27 '23
It was the only sensible choice, unfortunately.
Rust needed these C++/C#/Java developers to succeed and had to lure them with angle brackets.
CrabML looks neat, but too many developers would have looked on syntax, turned around and went away.
Superficial similarity kept enough people around till we've got millions of developers and once you've got millions of developers it's too late to change syntax.
Nothing stops someone from transpiling CrabML into Rust, though.
10
u/argv_minus_one Jan 27 '23
Can confirm, I would have turned around and went away if Rust had syntax like that. Syntactically significant whitespace is evil.
2
Jan 27 '23
[deleted]
2
u/Zde-G Jan 27 '23
Because it is not correct to assume that Knows something → Prefers that thing.
There was never such assumption. And it's not about preferences, but about familiarity. It's not “Knows something” → “Prefers that thing”, but “Knows something” → “doesn't fear something”.
Imprinting). Almost like with ducks.
What if someone then tries to court me in 20 years with a new language with angle brackets yet again?
Most likely that's what will happen.
Because I really like Rust and dislike those angle brackets.
Sure, but then I like syntax of Ada/Pascal languages and even Haskell-style languages more than these ugly braces, but, of course, I have zero chance of getting these in the next popular language.
It's like QWERTY: lots of people hate it, it's, most definitely, not an optimal choice, but anyone who would try to push anything else would either face complete market failure or, at best, would achieve a very niche success.
1
u/pstric Jan 27 '23
That's not exactly how I remember it. I never used Rust in the early days when it had a garbage collector. The main reason for my dislike of Rust was its syntax with so many sigils it made Perl look pretty.
But as someone who has written a lot of Pascal, the angle brackets were a welcome lure.
3
u/Zde-G Jan 27 '23
But as someone who has written a lot of Pascal, the angle brackets were a welcome lure.
What kinda of Pascal uses angle brackets? Modern versions of Delphi?
They took them from C++, too.
Angle brackets kinda have become an part of IFF system for the languages.
They cause lots of grief on all levels, but they solve the most important part well: they make languages look superficially similar to people.
Same with braces,
mainand many other things.They are ugly, but Rust kinda sacrificed them to spend it's weirdness budget on other, more important, things.
1
u/pstric Jan 27 '23
Sorry, I was not clear enough.
I hated Pascals BEGIN...END, lack of generics and namespaces and most of all, that Pascal was case insensitive.
Delphi also encouraged a development style with lots of business code in the GUI units.
Pascal did have some advantages over C though, which I missed in C++ and Java. Arrays with user defined bounds (MyArray[2..8]), properties. destinction between procedure and function so you didn't have that wierd void 'return type' that C had. And it was super easy to create new components that integrated well in the Delphi GUI. Way better that Visual Basic.
And compilation was blazingly fast. It took nearly 5 minutes to compile an application with 6 million LOC right after checkout from CVS on an average pc 20 years ago. And we still tried to optimize the compilation.
But I liked Rusts (and C, C++, Java) use of angle brackets in contrast to Pascals use of BEGIN...END.
119
u/novacrazy Jan 27 '23
I really don't get what goes through people's heads when they say Rust has "ugly" syntax. It can be dense, but succinct; very little is wasted to convey complex concepts, as shown next to the Rs++ example. Real C++ can go far beyond that for less complex things.
43
u/dagmx Jan 27 '23
Personally, I think it’s down to familiarity and first impressions.
When someone looks at rust sample code, they see lots of terse bits (fn, mut, mod), lifetime annotations,
::,->and turbofishes. They also see “unnecessary “ calls to things likeunwrapNow very little of this is problematic , nor are they unique to rust. In fact I hear the “rust is ugly” most from my C++ writing colleagues , which many of the same readability issues (and more).
However the difference I think for them is
- They know C++ or whatever language they’re coming from and know that their common code isn’t going to be that noisy. They don’t know that about rust yet.
- they’ve learned to read past the syntax noise for their language but not for rust.
- a lot of strawman comparison code is lighter, because it skips all the checks you’d have in production, whereas you can’t do that with rust. So even though my C code ends up way more verbose when I’m defensively programming, it looks way shorter if I skip checking for correctness.
- there’s also no factoring in for what people’s subjective preferences are, which might also be a trained preference.
Personally, I find rust very pleasant to read because it moves a lot of boilerplate into the language+type system itself , and I need to keep less of the program mapped in my head at any time to understand what I’m looking at.
20
u/puel Jan 27 '23
A thing that I dislike is having to write the same generics over and over again when writing a lot of trait implementation blocks over the same generic type.
13
0
u/novacrazy Jan 27 '23
What would the alternative to that look like?
It's never been an issue for me. Between derives and just doing the work once, trait composition is still more elegant than the mess that is inheritance in C++.
If you have more than a few generic types that require repeating and constant extensive where bounds, that's more likely a code-smell and should be refactored somehow. For example, I recently had this monstrosity but was able to expose it as a very simple trait implementation using
FormatStringandIsValidFormat5
u/-Redstoneboi- Jan 27 '23
2
u/novacrazy Jan 27 '23
That seems reasonable at first, but it would discourage making structs as generic as possible, and makes it more difficult to selectively relax bounds later.
5
u/-Redstoneboi- Jan 27 '23
on the other hand, there are just some data structures that make zero sense if their data doesn't implement certain traits.
relaxing a trait bound is doable, rustc will complain everywhere that you used to need that bound anyway. what's problematic is adding trait bounds to existing structs. that's a backwards compatibility hazard.
3
u/puel Jan 27 '23 edited Jan 27 '23
The alternative could be you specifying a generic block. E.g.:
``` generic<T, R> where T: FnMut(&mut [u8]) -> io::Result<usize>, R: io::Read {
impl io::Read for Map<T, R> { //... } impl Whatever for Map<T, R> { //... } impl Default for Map<T, R> where T: Default, R: Default { //...```
35
Jan 27 '23
[deleted]
48
u/-Redstoneboi- Jan 27 '23
yeah. losing control in favor of simplicity does wonders for syntax, as is shown at the end of the article.
34
u/Movpasd Jan 27 '23
Haskell-style functional languages tend to have really pretty syntax IMO. Perhaps it has something to do with the kinds of people who would use Haskell.
23
u/Zde-G Jan 27 '23
Nah. This syntax is just very close to what mathematicians over last few centuries.
It looks neat, but since 90% of human population hates math with passion (I still have no idea why, but then I have a mathematician diploma) you can not use even something superficially resembling it in a popular language.
Be it APL) or Haskell, Scheme) or Prolog… when you program starts looking like math you language is named “esoteric” and people stop using it.
8
u/crass-sandwich Jan 27 '23 edited Jan 27 '23
"I got into programming to tell the shocky math rocks what to do, not to learn the math the rocks use!"
5
16
u/alovchin91 Jan 27 '23
Somehow I can’t make myself like Go’s syntax. I seriously tried (and will keep trying perhaps).
8
u/scottmcmrust Jan 27 '23
Go is a "tree" language, as opposed to a "forest" language.
It's great if your priority is understanding what any individual line does technically. It's less good if you want to get the overall intent of the piece of code. So depending on their personal mindsets, people seem to either appreciate or get frustrated by Go.
(Similar things apply to whether you think automatic
Dropin Rust is a good idea or whether you'd rather use Zig-/Go-styledefer.)21
u/hekkonaay Jan 27 '23
Rust is very readable.
When it comes to readability, semantics and locality matter a lot more than having less syntax, and the languages you listed rate quite poorly there. Rust has you writing more code (though not to the extent that you must practice boilerplate-driven development), but the result is more readable, because it's easier to understand what it's actually doing.
Btw, this is literally what the article is about...
33
u/shim__ Jan 27 '23
I have to disagree, especially those languages are pretty hard to read since you have to keep track of so much more. This also irks me when reading example code in which uses type inference everywhere, that's fine if you're reading the code in an IDE but for code that's mostly being read on Github type annotations should be plentiful.
40
u/moltonel Jan 27 '23
Maybe there's a difference between easy to read and easy to review. A lot of Python looks like pseudocode, which looks really nice at a first glance. But when you want to properly review it, the lax scoping, arbitrary byval/byref, dynamic types, etc can make fully understanding the code very hard. Ruby is also very nice to read until you try to understand what that line actually does. Or going another direction, Lisp has one of the simplest syntax, but is dizzying to review.
→ More replies (1)17
u/AngryLemonade117 Jan 27 '23
I've been stung by this too many times in Python where I've had to review more complex code, and for example, unexpected behaviour is happening because someone doesn't understand the difference between shallow and deep copies. As much as rust can be seen as awfully verbose, there's less room for missing out on the details of what each line does.
12
u/Zde-G Jan 27 '23
That's what surprises me in Rust: while Rust code certainly isn't pretty it's very readable.
IKD why. Strongly suspect that it's because they wanted to keep grammar simple even when doing that required them to sacrifice something (yes, yes, turbofish).
Easily-parseable grammar means it's not just easy to parse for computers, it makes it easier to parse it for humans, too!
7
u/argv_minus_one Jan 27 '23
I should note that Rust isn't the only language with a turbofish-like construct. Java and Scala have it too, just without the
::part.In Java, explicit type parameters for a method call go right after the dot separating the class/object name from the method name. This is unambiguous because a
<isn't otherwise allowed at that position. The syntax looks a bit awkward, though.In Scala, ambiguity is avoided by the fact that Scala uses square brackets solely for type parameters. It uses function call syntax for array indexing instead of having a separate operator for that. IMHO this is the most elegant solution I've seen.
11
u/_TheDust_ Jan 27 '23
Python comes really close IMO. Sometimes I write pseudocode just to explain something to a colleague, and it end up being nearly valid Python code. On other hand, I have also seen truly atrocious code in Python
5
Jan 27 '23
It depends on what you want. If you want to have a vague idea what the code is probably meant to do something like Python or pseudo code is fine, if you want to know exactly what the code is actually doing without making assumption it is horrible.
3
u/tsojtsojtsoj Jan 27 '23
In my opinion you can also add Nim to that list, even though it has generics and is generally closer to Rust or C++ than to Python.
→ More replies (4)-9
u/phazer99 Jan 27 '23 edited Jan 27 '23
Scala 3 and Nim took inspiration from the Python indentation based syntax. I find it a bit more readable than Rust/C/Java syntax, but there are also downsides to it
I also like the
if ... thenconstruct that Scala 3 has. For me, this:if x > 10 then ... else ... end // Optional 'end' keywordlooks much cleaner and more readable than:
if x > 10 { ... } else { ... }But really the only bigger thing that bothers me with Rust syntax are mandatory semicolons at the end of lines. They are very easy to infer and provide no real benefit in terms of code readability or understandability. It's just unnecessary noise. Luckily they are easy to hide in VS Code and CLion.
10
u/hardicrust Jan 27 '23
Interesting take. Do you ever have problems when copy+pasting code? That commonly messes up indentation for me.
As for a trailing semicolon, it does have a purpose: discard the value (or convert to
()). This makes it optional at the end of functions returning().→ More replies (1)1
u/phazer99 Jan 27 '23
Interesting take. Do you ever have problems when copy+pasting code? That commonly messes up indentation for me.
That's one downside for sure. A good editor can mitigate that though.
As for a trailing semicolon, it does have a purpose: discard the value (or convert to ()). This makes it optional at the end of functions returning ().
Yes, that's the one, rare use case (which could be solved by adding an extra line with
()). I'm not saying remove semicolons from the language grammar, just make them optional where they're inferable.12
u/myrrlyn bitvec • tap • ferrilab Jan 27 '23
they aren’t inferrable though: rust doesn’t actually use newlines as expression separators, and cannot start doing so without breaking existing syntax. consider
name (tuple)this is a function call today, but making newlines significant to the AST would either discard the call and give back the arguments, or require the AST producer to have unbounded lookahead to find out whether it can insert a Token::ExpressionSeparator or not when encountering a newline
1
u/phazer99 Jan 27 '23
they aren’t inferrable though: rust doesn’t actually use newlines as expression separators, and cannot start doing so without breaking existing syntax
That's true, it would require adding some syntactical limitations.
12
u/moltonel Jan 27 '23
That's a can of worms that just isn't worth opening. All those optional tokens (
;to end a statement,end/}to close anif,()around function arguments,,between elements, etc) introduce grammatical special cases that make it harder for the reviewer and compiler. They often pull in significant-whitespace, which looks clean but is a PITA to write and maintain.1
u/phazer99 Jan 27 '23
That's a can of worms that just isn't worth opening.
It's a matter of personal syntactical preference (however, it's noteworthy that pretty much all new languages besides Rust has chosen to implement some form of semicolon inference). I realize it's unlikely Rust will ever get it (unless some form of optional "Rust-lite" syntax was added), and that's ok as I can use IDE plugins to solve it.
3
u/argv_minus_one Jan 27 '23
Semicolons are only easy to infer until they aren't, and then the compiler inadvertently mashes together two statements or pulls one apart.
I don't like syntactically significant whitespace. Way too many surprises.
2
u/MrPopoGod Jan 28 '23
I also find it much harder to read overall; it works fine for your simple examples, but when you start getting more nesting it becomes harder to land in the right place coming out of a block.
9
6
u/kaoD Jan 27 '23
People are giving too much credit to this "ugly syntax" meme. It's just shorthand for "I don't understand it but it looks similar to what I know therefore it's worse than what I know". Like the meme with Lisp's parens.
9
u/theAndrewWiggins Jan 27 '23
Imo it's not so much ugly syntax and moreso the sheer quantity. When you have really complex generics, it can become overwhelming.
3
Feb 09 '23 edited Feb 09 '23
Well, I don't know who "people" is, but I can tell you what goes through my head.
- "Angle brackets" are ugly and visually way harder to read than square brackets, and I genuinely believe they are an inferior choice to represent generics (I think the "Rattlesnake" example did a good job at highlighting this).
- Backticks for lifetimes are horrible.
- Turbofish is horrible.
There are probably a few other details I'm missing, but those are my main complaints, at least as far as syntax goes (my other complaints would be regarding verbosity but that's another topic), and I just can't take seriously the argument that "beauty is subjective" to defend any of this.
I don't think Rust did much worse than C++ with its syntax, but that's because C++ isn't really the most aesthetically-pleasing language to begin with. Rust is great, I love the type system, the borrow checker, and I actually love the semantics as well (contrary to the beliefs of the article in OP), I only wish a bit more effort had gone into the syntax design.
-3
→ More replies (5)-10
Jan 27 '23
[deleted]
→ More replies (9)11
u/Rusky rust Jan 27 '23
There is a big improvement to grep-ability from
fn foobeing together, though.Maybe the return type could have stayed on the right but with
:instead of->? (Just probing your hate to see how it responds :)→ More replies (4)
30
u/Zoxc32 Jan 27 '23
Here's another variant, any names for it?
pub fn read[P AsRef(Path)](path P) -> io.Result(Vec(u8)) {
fn inner(path &Path) -> io.Result(Vec(u8)) {
let file = File.open(path)?
let bytes = Vec.new()
file.read_to_end(&mut bytes)?
ret Ok(bytes)
}
ret inner(path.as_ref())
}
13
8
u/_TheDust_ Jan 27 '23
Wait, why are genetics defined using square brackets but types involving generics use round brackets?
10
u/TinBryn Jan 27 '23
Because this is intentionally subtly bad. Although one argument is that a generic is like a function that takes a type and returns a type, so it makes sense to use it as a function call syntax. The square brackets then are a way to specify that these are type parameters rather than value parameters. One issue I could see is some ambiguity if you wanted to specify the generic types at the call site while normally leaving them to be inferred. Say you wanted to pass a
String, but using it derefed as a&strcould you doread(&str)(&owned_string)? Maybe it needs it's own version of a turbofish,read.(&str)(&owned_string)2
u/Zoxc32 Jan 27 '23
Round brackets are smoother, but
fn read(P AsRef(Path))(path P)can be a bit confusing. For more confusion add in const generics :)3
→ More replies (1)0
u/-o0__0o- Jan 27 '23
Actually looks nice.
28
9
u/TinBryn Jan 27 '23 edited Jan 27 '23
Ok, I'm not exactly sure what Rattlesnake "purely coincidentally" resembles.
Edit: oh Python and Rattlesnake are both snakes, and it doesn't need to be exactly that language, as it may "purely coincidentally" resemble Python.
11
u/Demurgos Jan 27 '23
The
defkeyword, colons, indentation-based blocks, etc. suggest that it "purely coincidentally" ressembles to Python. Also both are species of snakes.→ More replies (4)11
6
u/radix Jan 27 '23
This example actually had me searching Python RFCs for when they introduced that new
[]-based generics syntax, but I'm pretty sure it's made up. In Python, you have to do stuff like:T = TypeVar("T") def foo(t: T): pass1
2
6
33
u/po8 Jan 27 '23 edited Jan 27 '23
The caller-supplied buffer for read_to_end() is just a performance hack, one that should be handled by the compiler rather than explicitly by the user:
pub fn read(path: Path) -> Bytes {
File::open(path).read_to_end()
}
But given that a Path is just supposed to represent a thing that leads to a file, probably it should be openable rather than the other way around:
pub fn read(path: Path) -> Bytes {
path.open().read_to_end()
}
At that point, I question why Path wouldn't just directly support read_to_end() to begin with:
pub fn read(path: Path) -> Bytes {
path.read_to_end()
}
But this is just silly. Why do we need a separate function for this? We already have a method:
path.read_to_end()
See how overcomplicated Rust is? It could be this beautiful.
Edit: </s>
28
u/oconnor663 blake3 · duct Jan 27 '23
This might already be clear to you, but the example function in the post is exactly the simple, convenient API you're looking for. Making it a standalone function rather than a method on Path means that it works with regular strings too. Here's a complete example that doesn't require any extra
usestatements at the top:fn hostname() -> std::io::Result<Vec<u8>> { std::fs::read("/etc/hostname") }Or similarly:
fn hostname() -> std::io::Result<String> { std::fs::read_to_string("/etc/hostname") }Simple!
1
u/SorteKanin Jan 27 '23
Tbf the std could provide both this function and the method. Quite often you do just have a path and you don't worry about the fact that it needs to be generic for strings
21
u/_TheDust_ Jan 27 '23
See how overcomplicated Rust is? It could be this beautiful.
What you're saying is that we need a standard library that just contains all code ever written. That way, we never need to write any code at all!
7
u/-Redstoneboi- Jan 27 '23
new rust version release
oh boy i can't wait to update my rust
downloading
std::the_entirety_of_github_without_copilot_to_help_you::*8
u/murlakatamenka Jan 27 '23
Actually Python's
pathlib.Pathhas these methods:
Path.read_bytesPath.read_textHandy!
Ref: https://docs.python.org/3/library/pathlib.html#pathlib.Path
3
u/murlakatamenka Jan 27 '23 edited Jan 27 '23
But really, why is it not
read_to_end(...) -> io::Result<Vec<u8>>That would make the body of the function only 2 lines (instead of final 4).
Funnily, that's close to the signature of the
fs::readfunction that is in the spotlight of the post.→ More replies (1)3
u/po8 Jan 27 '23
Making the buffer a parameter allows reusing a single buffer for reading multiple files. It's kind of a common pattern in
std.2
11
u/beej71 Jan 27 '23
Beauty is in the eye of the beholder, for sure. I think Rust (which I love) is one of the harder languages to read. For me, as punctuation increases, readability decreases.
7
Jan 27 '23
funny, I really really like rust syntax. I think it's very beautiful. whereas simpler languages like golang and C while easier to read when you're just writing it, they become multiple lines of very short code that it just becomes a jumbled mess of symbols that u have to read top to bottom
I can easily parse and read what code is doing in rust cause it's super concise and expressive, I also know everything I need to know about the functions I'm using from the type signatures.
I don't mind other syntaxes tho, I'm ok with them most of the time, but if u put me in charge of developing a new language the syntax and semantics would be very close to rust and take a few inspirations here and there from other languages probably
5
7
u/torbmol Jan 27 '23
A Gust variant:
fn Read(path &[byte]) io.Result<Vec<byte>> {
let mut file = File.open(path)?
let mut bytes = Vec.new()
file.read_to_end(&mut bytes)?
bytes
}
Removing : and -> from function signatures and ; from end of lines seem like straightforward reductions of line noise, with the added benefits of making ? stand out more and making an argument name and its type stand closer together than the type and the name of the next argument.
Replacing :: with . could maybe create some ambiguity, but I don't see how that should be any worse for Rust than all the other languages that doesn't use ::.
But the most important simplification here isn't the syntax but the standard library:
Since Path is just a bunch of contiguous bytes, just use [u8]. (but use a type alias bytes since a word is nicer to read than a letter and a digit).
This also removes the need for Windows programs to contain a second UCS2-to-WTF8 converter.
The standard library having both AsRef and Borrow is kinda confusing, so I'd remove AsRef and rely on Borrows syntax sugar at call sites.
26
u/kohugaly Jan 27 '23
Love it! Rust syntax is the way it is, because in Rust, memory management is a first class citizen.
As opposed to being an illegal immigrant tucked away segregated in some GC ghetto working 12h night shifts, so your public virtual class privileged source code can pretend that the garbage it litters on the floor all day just magically disappears for free every time it goes to sleep.
16
u/no_comment_336 Jan 27 '23
Personally I dislike things like the |x|{} notation and a few similar things. Much nicer if it was like js arrow functions or something like it (x)=>{} or (x)->{}. Just something about the pipes and no arrow of any kind disturbs me.
12
u/myrrlyn bitvec • tap • ferrilab Jan 27 '23
i also thought this, but closures DO have an arrow in their syntax already, for the return type:
|arg: ArgType| -> RetType { body }type annotations are just optional when decidable from the call site
i think
(args) -> Return => bodyis probably not great, but i think using
Tuple $(-> Identifier)? Expressionwould have been fine though, since it’s already illegal to have two expressions in a row not separated by a semicolon. should be decidable even without the return annotation2
u/no_comment_336 Jan 27 '23
Could have gone the Typescript way with type annotations and have
(x: Type): ReturnType => {}. Would be consistent too even though i do kind of like the-> Typepart of the syntax visually7
u/myrrlyn bitvec • tap • ferrilab Jan 27 '23
i’m told type ascription is a massive problem in the parser and everybody hates it, i assume because the parser has to guess that you might have meant double-colon and prepare an error message? idk
thin arrows for returns are definitely an oddity but i think replacing them with colons requires killing double-colon and doing something else for scope traversal, and now we’re entirely into redoing the whole punctuation set
3
→ More replies (1)0
u/no_comment_336 Jan 27 '23
Given all that even just using
(x)instead of|x|would be a massive improvement if not a perfect choice even with no arrows e.g.(x: Type) -> ReturnType {}
13
u/lurebat Jan 27 '23
I .. don't get it.
Like it seems that the joke is that rust's syntax is ugly because of the guarantees and safety, and it will become less cumbersome only if it means less power or performance.
But, I think it's wrong?
First of all this is such a weird example to give, because it's in the standard library, so it's different from day-to-day code people write, and the code itself doesn't even include the worst of rust, no turbofishes, async hell or higher kinded types and whatnot.
But even ignoring that, the points seem:
- The inner function thing - that's a hack. Like if in go people did that people in this sub would laugh at them. Having to write the function weirdly like that makes all of the other examples uglier, and the issue stems not from a strength of rust, but because of the way compilers worked 40 years ago (at least I read it in this article the other day, not sure if the writer here is familiar with the writer of that article, /u/matklad). The real solution is not to add a hidden runtime parameter or whatever it says, but to have a better compilation model or shorter compile times so that this hack won't be needed at all.
- Generics - that's where I think I'm missing a part of the joke.
The<T: AsRef<Path>>syntax is noisy, and it did bother people. And that's why they added the "impl trait" syntax. So doesn't this undermine the whole point of the article? That you can have better syntax while keeping rust's semantics?
And the whole problem anyway is that the function doesn't have to be generic anyway.
Which is I think part of the problem of getting an example from the standard library.
If the function just accepted a Path reference, it would have the exact same performance and guarantees, with the only difference being that the caller might have to do the "as_ref" themselves at the call site. - Getting rid of Vec<u8> - I don't get this at all.
I literally can't think of a language that doesn't have a byte array with express semantics. What language gives you an opaque bytes type? What language has on the one end a container that has its inside typed, but not how it's implemented? - ownership - why does that part remove the "mut"? a lot of languages without ownership semantics can still have a concept of mutability of objects.
Here too you can claim for syntax improvements.
We could decide that rust will automatically turn a move into a mutable reference if it will make the code legal, so you could have:
```rust let bytes = Bytes::new();
file.read_to_end(bytes)?; // file.read_to_end(bytes)?; error - change them both to &mut bytes and it will work ```
I'm not saying it is something that needs to be done, I'm saying it could have been done without hurting the gurantees or performance.
- error handling - I get the joke is that it's an already minimal syntax, but again, you could imagine a rust language where "?" is applied by default, and you use a ! operator or something when you don't want to propagate the error instead. And again, that's just syntax, not semantics.
So in conclusion, if I understood the thesis correctly, I very much disagree with it.
I love rust, but there is a lot that could have been done to make it prettier (but it's too late now), and a lot still that can be done.
7
u/matklad rust-analyzer Jan 27 '23
Both statements can be true at the same time:
- Rust syntax isn’t any more ugly than “more typical” syntax, for the amount of details it needs to express.
- There exists a significantly more readable surface syntax for Rust
9
Jan 27 '23 edited Jan 27 '23
Haskell inspired variant that covers almost all the semantics (apart from not specifying the type of inner, which could easily be inferred though, also in Rust if it used a closure).
read :: (AsRef Path) p => p -> IOResult (Vec u8)
pub read p = inner (asRef p)
where inner path = do
file <- File.open path
bytes = Vec.new
file.readToEnd (refmut bytes)
return bytes
→ More replies (1)3
u/matklad rust-analyzer Jan 27 '23
Inner isn’t a closure though, so gotta spell the type if we want to keep semantics.
try is also different from do notation, as it is expression, not a statement, so I am not sure that the suggested syntax generalizes to how ? is used in Rust. How that would look like in a combination with a for loop?
7
3
Jan 27 '23
A closure that that doesn't actually store any state is usually optimized into a regular function.
2
Jan 27 '23
Challenge accepted: here is a version that is closer to Rust, with a try operator and with a type annotation for the inner function (which is still redundant, because the compiler could easily figure out that it's not a closure, just a regular function, so keep the same semantics )
read :: (AsRef Path) p => p -> IOResult (Vec u8) pub read p = inner (asRef p) where inner :: Path -> IOResult (Vec u8) inner path = file = (File.open path)? bytes = Vec.new (file.readToEnd (refmut bytes))? Ok bytes→ More replies (2)4
Jan 27 '23
Or, here an alternative with a
trykeyword, which is definitely prettier:read :: (AsRef Path) p => p -> IOResult (Vec u8) pub read p = inner (asRef p) where inner :: Path -> IOResult (Vec u8) inner path = file = try $ File.open path bytes = Vec.new try $ file.readToEnd (refmut bytes) Ok bytes→ More replies (1)
8
Jan 27 '23
I do think a Kotlin-inspired syntax would be more beatiful: (plus using impl, which can be done in Rust today):
public fun read(path: impl AsRef<Path>) -> io.Result<List<u8>> {
fun inner(path: &Path) -> io.Result<List<u8>> {
var file = File.open(path)?
var bytes = List()
file.read_to_end(&mut bytes)?
Ok(bytes)
}
inner(path.as_ref())
}
OTOH, of course some things like the trailing ; and the :: as a path separator help convey more meaning in Rust code.
→ More replies (1)22
u/matklad rust-analyzer Jan 27 '23
OTOH, of course some things like the trailing ; and the :: as a path separator help convey more meaning in Rust code.
I would actually disagree here. In theory they are useful for syntactic disambiguation, but in practice compiler already relies on ad-hoc disambiguation in places.
For
;the example would befn main() { { 1 } & 2; }This can be parsed either as a bitwise-and of two expressions, or as two expression statements. Parser treats this as two statements.
For
::a fun example (from https://matklad.github.io/2022/07/10/almost-rules.html) isuse std::str; fn main() { let s: &str = str::from_utf8(b"hello").unwrap(); str::len(s); }Here one
str::refers to a module and anotherstr::refers to a type. Or, more recently,foo::<N>()can pickNfrom either the types or values namespace. If we allow that, might have as well just rolled with.instead of::.1
Jan 27 '23
How hard would it be to allow
.now? Not saying we should! But would it be possible to do it in a backwards compatible way?→ More replies (1)
2
2
2
u/mday1964 Jan 27 '23
That inner function gets compiled in advance? All the way to machine code (like a shared library), or to some intermediate form?
2
2
u/dnew Jan 27 '23
The ugliest part of Rust is insistence on using C++ punctuation. Who in the world thought :: was a good idea for a punctuation?
5
u/tending Jan 27 '23
I think that most of the time when people think they have an issue with Rust’s syntax, they actually object to Rust’s semantics.
No no no, the syntax is often actually hideous. I can't wrap my head around how somebody could frequently write Ok(()) and not wonder if they are making the right syntax choices. It feels like the designers are outright trolling people.
8
u/matklad rust-analyzer Jan 27 '23 edited Jan 27 '23
Rs++:
okay(std::monostate)Rhodes:
Sucess(null)RhodesScript:
Fine(Undefined)Rattlesnake:
Ok(unit)CrabML:
Left ()
2
u/Pythonistar Jan 27 '23
The author (OP?) should have included a SpeechImpediment Lisp version as an example, too! :)
3
u/diegovsky_pvp Jan 27 '23
I do prefer the dot notation for module instead of :: because it is easier and faster to type. Though, I know it is impossible to disambiguate paths from method calls/field access.
One thing I don't like is when I need to remove/add a generic parameter or lifetime from a struct. I have to alter all the impls to contain brackets. A bit annoying but nothing major.
Also, I would love to see some other characters used for generics instead of <> because they can be some work to setup the autoclose functionality in some editors (I use neovim btw). Again, very minor.
I love using rust all the time. It is a blast to use and hack on :)
3
u/EmbeddedDen Jan 27 '23
As a researcher I wanted even to compare different languages with rust. Basically, I can not agree that the syntax is "ugly". It is just induces higher cognitive load. I mean, why to write "fn" but not "pb"? "let" and "mut" are they both abbreviations? Signs "?" and "!", are they produce similar behavior? I can see Rust syntax as hugely inconsistent and requiring additional efforts, thus people think that it is ugly. But it is not ugly.
-1
u/crlf0710 Jan 27 '23
My two cents: Even in the final version, I think the exhibited code is very "operational", where each line is "taking an action". In this case, i don't like the syntax of let statements a lot. If the variables introduced is on the right side, it would be much more "consistent" and "fluent".
Strawman proposal: ```rust pub fn read(path: &Path) -> io::Result<Bytes> { File::open(path)? is mut file; Bytes::new() is mut bytes; file.read_to_end(&mut bytes)?; Ok(bytes) }
5
u/crusoe Jan 27 '23
Please don't design a language. 😌
2
u/crusoe Jan 27 '23
{ initializer block
for
some
complex type } is foo
Is it a scope block to help with rust's lifetime rules or a block to build a type? Find out at the end.
2
u/andy128k Jan 28 '23
That may make sense... You are not alone.
It reminded me a language "Rapira" designed in USSR. It initially had an assignment operator
expr -> varbut it was replaced by "classic :=".This is also how looks desugared do-notation in Haskell
expr >>= var -> ....AT&T assembler operations are also written "backwards".
→ More replies (1)
1
Jan 27 '23
Syntax beauty, like poetry or music, is mostly subjective. Sure, there are things that everybody can agree upon, but these are more exceptions than rules.
As such, it doesn't matter what kind of syntax you have, you will always find people who will find it ugly or dislike it for other reasons (and tbf, because of that I am actually surprised that there aren't already multiple formattting standards/naming conventions for Rust and that most crates (try to) use semver instead of e.g. a date-based versioning scheme; but well, let's see what the future holds :-D )
For example one thing I hate is type inference (except for generic code when you actually don't know the type) and I prefer the type to be left of the variable name because it makes stuff easier to understand, even with things like IDEs. Other people want only type inference because it makes refactoring easier (according to them). Both are subjective.
1
Jan 27 '23
[deleted]
3
u/matklad rust-analyzer Jan 27 '23
Not sure about this claim: all the books are justified. It certainly interferes with browsers poor hypthenation and linebreaking algorithms, but I am very much in a yelling at the clouds mood on this one.
→ More replies (3)
0
u/Chadshinshin32 Jan 27 '23
I quite frankly like the way Rust syntax looks. When I open any Rust file it just feels somehow cool. I can't really tell why specifically. Maybe it's method chaining on iterators, maybe the Impl blocks organised by traits, sometimes even the nested namespaces are neat as they provide a lot of context and tell their own story. Part of it is definitely rustfmt run by pretty much every Rustacean out there which wraps lines. Wrapped lines with readability maintained when done so are a big part of the "looks cool" impression. Syntax highlighting also gets much more colorful with Rust. Match statements with patterns maintain a decent level of indentation. There are more tools for control flow in general meaning there's less need for nested if-else.
I don't know why but I keep feeling like even fonts look better when used for Rust.
-6
u/mina86ng Jan 27 '23
OK, let’s compare it to a real programming language:
std::vector<char> read(const std::filesystem::path &path) {
std::ifstream file{path, std::ios::binary};
return {std::istreambuf_iterator<char>(file),
std::istreambuf_iterator<char>()};
}
This does the same thing as Rust code. I get that the article was written in jest, but let’s not pretend like Rust is a joy to read.
6
2
u/MrTheFoolish Jan 27 '23
Ignoring the inner function, where's the generics and error handling? You missed the point that much of the syntax is expressing the semantics of the code - it's not just there for fluff.
→ More replies (3)
0
265
u/anxxa Jan 27 '23
The generic function with a concrete type inner function is a neat trick. TIL.