Ask HN: Why aren't modern programming languages, like Rust, more legible?
I'm not afraid of programming in general, being a data analyst by trade I often use Python and SQL in my day job. I've trained on Java, and C# (though admittedly with some difficulty) and had dabbled in Delphi (Object Pascal) and Visual Basic as a little girl so the concept of programming isn't new to me. Perhaps the 'low-level-nes' of Rust, a language I would love to learn, is new to me which is perhaps one of the reasons I'm having some trouble understanding it.
I'm struggling with heavy implementations of symbols, and perhaps 'shorthand' reserved keywords in languages like Rust, C++ and to some extent JavaScript; I find they aren't as readable as 'higher level' languages like Python.
Given that Rust is one of the newer languages on the block, Python is 21 years it's senior, was the syntax of Rust determined by performance factors or because of it's 'low-level-ness'? Are languages like Python and SQL more legible because they're interpreted and compiled into bytecode, rather than just compiled into specific machine code? Or is it purely a decision of the language's developer?
This question is focused on Rust specifically, given that's a newer language, perhaps wondering why it didn't adopt some of the design principles of easier to read languages, though I am certainly not singling it out. Are there ADHD friendly tutorials/books on Rust?
78 comments
[ 1.7 ms ] story [ 137 ms ] threadThere is a tradition of using symbols in languages of the C family. Rust is in that space because it addresses the same kind of problems that C solves. Familiar syntax helps developers to switch language.
I guess that a Pascal like syntax for Rust is possible. Would C developers have liked it more or less than a C like one? My take is: less.
An example of a compiled language with little use of symbols: COBOL.
It's worth mentioning that Python, for example, can still achieve good performance through the use of optimized libraries and just-in-time compilation techniques.
Modern Pascal provides basically as fine-grained control over memory management as C, while not needing "complex syntax and symbols", really.
It's also a big factor of why I enjoy programming in Nim, which provides low level performance but with minimal use of sigils. Though I also appreciated Julia's syntax as well that also provides good access to performance.
Rust is plain hard and complex. There is only so much you can simplify that.
Like, you can make jet fighter easier by adding ways to automatically make some things so pilot doesn't have to do all the steps (like say Rust does with some of the memory management), but it will never be as easy to fly as it is to drive a bicycle, still need to learn how everything works and what does what
I'm here doing things like creating an object, iterating over some structure, building a simple function, reading or saving some data, making a small REST API - and while I'm doing these things I have to work with complex syntax and language semantics because the same language also caters to use cases that have nothing to do with the thing I'm working on.
'?' doesn't mean anything like what it means in every other programming language, and it's everywhere. It is so frequently used that you get used to it pretty quick, but it is very not obvious to a beginner.
Obscure #[blah(blah)] attributes attached to nearly every line of code in some places. Do those have meaning? Are they just hints to the compiler? You'd better hit the docs to find out.
Do I need a &mut Option<Thing>, or an Option<&mut Thing>? And how do I get that out, as a `opt.as_ref().mut()`, `opt.as_ref_mut()`, or &mut *opt`? Are these valid? What's the difference? Idk, it's inscrutable.
Rust syntax really, really sucks my friend.
Follow up to this example: let's say that I don't know the answer, what do I search for if I want to find out?
The only thing that keeps this from being as big an issue as it ought to be is that rustc’s error output is really, really good. Usually it tells you how to fix it.
The second part of your question is bad syntax however, I agree (but still, 90% of the unscrutability of rust is caused by its concepts imho).
That depends on what you want. Do you want the address to a mutable Option of a Thing, or do you want an Option of an address of a mutable Thing? The mental model you have of your code should make this rather clear.
It's a complex language with unique properties, and it's syntax was carefully crafted towards it's goals - high performance, close to hardware, no runtime, memory safety etc.
I have ADHD. I also wonder about this.
I take my time digesting the content. Mainly this is because I process text slower than average. I always prefer short sentences. For long sentences, I split them up and interpret each part.
Books for kids are usually written with short sentences. Maybe tutorials "written for kids" might be an answer? But that sounds condescending. So maybe it is not the answer?
Finally I will add that I firmly believe this. It is more about "how you consume the content" and less about "how the content is made for you".
The opposite end of the spectrum is Go, which I love, but many hate for being too limited. I like seeing everything in the control flow represented as values - context and errors included - but too each their own. I'm not sure if you need to learn Rust for work or are just interested in a new language, if it is the latter I can recommend checking Go out. :)
And personal preference here, Rust is, with a few exceptions, actually one of the nicer languages to read once you add/remove all the extra information that rust requires.
https://matklad.github.io/2023/01/26/rusts-ugly-syntax.html
(But I am personally not bothered by Rust’s syntax)
It's a ridiculously noisy signal, which makes it difficult to distinguish from noise: because all the error handling looks the same and is extremely verbose, it's difficult to distinguish special cases you need to pay attention to.
Tons of languages have "errors as values", Go stands out in its verbosity, as well as its lack of safety / certainty.
As a dev though I infinitely prefer Go’s error handling than exceptions for instance, specifically because of the verbosity and explicitness
It's amazing how every symptom became ADHD when unscrupulous VC-backed startups took the pandemic as an opportunity to start marketing and prescribing ADHD medication online.
I will say though it's interesting how in the US ADHD seems to be as common as hay fever whereas in the EU seems to me that it's very rare to be prescribed for ADHD.
However, just going by definition of Attention Deficit Hyperactivity Disorder, continued focus is an issue. When you have code like below where a bunch of attention/focus might be needed to just to parse it/keep it all in your head and those things are biologically in deficit, I can imagine it won't be smooth sailing:
PS: for rustaceans, this is made-up code to make a point so please don't try to optimize it.That’s a very strong negative claim that deserves citation.
Why are moden programming languages, like Go, so plain?
Don't think that's ADHD bud.
I have less of a problem with Rust than Swift for example, because Swift is really hard to decipher at times. You can leave out chunks of code and it's supposed to be obvious what it does but it's not. The whole language can become dialed down to looking an like a DSL; for example SwiftUI. It's confusing as hell, e.g. Button(action:{}, label:{}), Button{} {stuff} etc.
Rust is certainly a mouthful to look at, but it looks like Rust. Python's syntax is a big part of python's success. However I find that personally, I don't like meaning through indentation. I prefer the safety of `;` and `{}`.
My advice is spend more time writing Rust, eventually your brain becomes selective at reading things that matter and ignore things that do less, in context of what you're looking for, whilst reading.
An untyped language or a language with a small type system and type inference like SML always looks more readable.
If things get more advanced, the syntax will grow. Now, whether Rust needs to look that ugly is another matter. I think they made some wrong choices and should have evolved more from SML etc.
Whether it's a C, a Lisp, a ML, or an APL — spend enough time getting your hands dirty with it and you just get used to it.
My favourite languages are Python, Java and C.
I have written 2 Rust programs. I am not sure I understand C++ "forward" and Rust's lifetimes with the apostrophe. Any explanations would be helpful.
Async Rust, traits all seem hard to understand to me. I would like to understand them better.
I like C programming because it feels simple.
With Rust it feels that everything is a fight, I haven't yet got the mental model to design Rust programs. I think I need to design programs as trees.
I can deal with * symbols and &.
I find into(), forward hard to understand.
I find C++ template syntax hard to understand, things like unique_pointer.
Where C++ gets weird is that you can’t instance something without it being fully known (the full definition needs to be visible). This is rooted in the .cpp / .h header split, which as far as I know is pretty unique to C.
https://en.cppreference.com/w/cpp/utility/forward
It has something to do with references and lvalues.
I'm not trying to gate keep, but if you don't understand unique pointers and templates in general, you're a long way from needing or being able to make use of perfect forwarding imo. You don't need perfect forwarding outside of generic wrapper type code, and in most code you can just accept a specific type of object (e.g., value types or const references).
If you want to understand it you should start with learning value categories, move semantics, and templates, and then try to learn perfect forwarding.
In C++ behaviours are named, like in some C++ code coroutines there is a template that looks like:
template <is_task task_t>
The is_task looks like an if statement.
Normal template code looks like:
template <typename T>
Where you can also choose to use the class keyword instead of typename.
C++20 isn't very widely used though, so this isn't something you're likely to run into in anything other than cutting edge code. If you want you can read about them here: https://en.cppreference.com/w/cpp/language/constraints but you're probably better not using concepts at least at first. In general, https://en.cppreference.com/ is a very high quality source for learning, although it's very dry as well.
Work your way through the Rust Book, but do it slowly. It is all online. It is what I am doing right now. Here is a link for you. https://doc.rust-lang.org/book/
When your read a program it puts cognitive load on your brain. How much is a bit depended on your education and experience on the subject, but what you have to do is to transfer what you are reading to your mental model what the program is actually doing. Your capacity of things you can process in your brain is limited so you can fairly assume that the more load is put on your brain the more of that capacity is used, and the nearer your are to your maximum capacity the more difficult and exhausting is the task to understand the program.
When you need to decode a "{" or a "@" in the program to a actual processing step. you can assume that
is more difficult to decode compared to simply the latter one is nearer to your natural language (I actually think to replace "ELSE" with "OTHERWISE" would be even better) and the "ELSE" implies both the opening and the closing braces.You can argue if
is more readable, I would argue it is not.It's not like special characters are always bad. For example
works quite well when a is pointer and the arrow "->" symbolizes graphically where a is actually pointing at. But nowadays we languages which use "->" and "=>" for different things and for their similarity they interfere which each other when doing the decoding and adding to the cognitive load.When designing a language you have to trade of brevity or number of characters to type and readability. Most, if not all languages, nowadays opt for brevity because it appeals to the developers to type less and makes it more easy have more complex functionality in one statement. The price you pay is readability and finally maintainability of the program.
I think the first programming language which failed by its overuse of badly readable syntax was Perl, which was replaced by the much more easy to read Python.
I personally believe the psychology of programming is an under-researched subject
Disclosure: Alongside to CS I studied cognitive psychology when I attended university.
Are there really studies that backup this claim? I _feel_ that I process programs and natural language very differently, that I don't look for the same things at all. I don't have a need to "read" the program as if it was text. I don't look for a natural grammar structure in it. And English is not my primary language, and it's not for a lot of people. But then, I've never studied cognitive psychology, and I cannot really say I trust what I think I know about how I process programs.
As I grew more experienced though, this has shifted. I took some time to really learn C and C++, and while I still use Pascal in my day job (Delphi), I now feel the natural language keywords "get in my way" a bit, and I prefer the brevity of curly braces and such.
For me, this isn't about typing, I don't think that's a very interesting metric and I spend far more time thinking about my code than writing it regardless of language. The milliseconds saved typing "{" vs "begin" is irrelevant to me.
For me it's about parsing. Once I trained my brain on the curly braces, the begin/end pairs take so much space so to speak, they kinda obscure what's really interesting.
But before that, the natural language keywords helped me because my eyes and brain were trained to recognize them already.
That said, I've looked at Rust code and yeah, I'm having that initial C/C++ vibe again, where it all looks like a mess of "random" symbols thrown together. I'd have to spend some serious time learning Rust for that to look acceptable I think.
My big issue with python (and others like plain JS) is they don't type things and it's easy to forget what is what
Also Rust program are usually small, in the other hand in Python I see objects that extends from 6 objects and they can be as big as 1k lines with data inside
If you really have ADHD after 10mins reading untyped python how can you remember if you are passing a dict or an object? or what it was 200 lines above of the same object ? or what it has the 6 other objects that you are extending from?
Even with types the multiple inheritance example you describe is incredibly frustrating and near impossible to grok.
I love huggingface's transformers library and use it every day, but trying to find out what the fuck is actually going on when I 'call' a model instance (yes, they overloaded __call__) is a kafkaesque journey through the inheritance tree. I must have tried like 5+ different times and I still don't know what's going on.
edit: sorry for the rant, it's just an example of multiple inheritance and python pain.
That said, Rust is okay because it uses symbols, but not okay because it uses them in very weird ways. Macro function stuff that needs a ! like whatever!() is aggravating. The exclamation mark is easy to overlook, easy to mix up with letters, isn't surrounded by spaces and frankly I do not see the point. That syntax doesn't really add anything to the safety of the language that couldn't just be done with a few 'you are overriding stuff' warnings. Actually it makes things worse, because if somebody has stuff() and stuff!() defined, there is hard-to-distinguish weirdness going on. Also aggravating but not as dangerous are 'labels. Oh, and angle brackets like <'stuff> that could easily also be comparison operators somwhere else. Rust would have had the opportunity to fix the mistakes in C++, but it just copied them.
Oh, and all the 'mut' everywhere is also weird noise that would be easier if left out. Most things are 'mut', so write out 'const' instead.
Mut makes the unsafe case stand out. It also requires less typing to have the safe case.
However, the former also leaves so many details unclear. Like, how long should it last? Who provides the design? How do you inspect the end result? How much should you pay up front, and how much afterwards? Who buys the materials, who pays for them? What happens if they accidentally burn your whole house down? And a hundred others.
Python and Rust are like that. Python is very legible because it doesn't specify a lot of things. What is the type of that variable? Is it declared or not? Both of these are fixed with modern linters and type hints, but type hints already make the language less "legible". Is the code that I'm calling able to modify the data I pass in? How exactly are the objects allocated in memory?
Once again, many of these concerns can be unimportant for small projects, just as simple verbal agreements are enough sometimes. But for huge software systems, especially ones where memory layout and performance are critical, all these details are crucial — just as a good contract is.
You could get a decent IDE to fill in the gaps for you. Otherwise make lots of snippets and save yourself the mental overhead while you get started on making things.