5 ms·
What happened to the readability of code? This looks dreadful. I am sure there is a way to come up with a better syntax without sacrificing the type safety aspe
by fermienrico 9y ago
What happened to the readability of code? This looks dreadful. I am sure there is a way to come up with a better syntax without sacrificing the type safety aspects.
fn main() {
let my_str: Rc<RefCell<Box<MyStruct1>>> =
Rc::new(RefCell::new(Box::new(MyStruct1)));
my_trait_fn(Rc::new(RefCell::new(Box::new((**my_str.borrow()).clone()))));
my_str.borrow().my_fn();
}
I come from a Python/Java world and I remember when I first delved into embedded MCU programming in C/C++, it was a cognitive overload and a half. I think we need to take a step back and while I understand the differences between high level languages (Java/Python) and C/C++ having the burden to allow for low-level programming; readability aspects of code often gets the lowest attention.
If we, humans, spend so much time writing/parsing code through our vision system, programming languages need to take this aspect seriously and optimize the syntax over readability than expressivity.
I recall Kernighan's "The Elements of Programming Style" where he talks about making the code more understandable than "clever". The syntax of a programming language directly facilitates (or impedes) this idea.
Edit - Everyone seems to keep pointing out it is a contrived example. Yes, I agree but the syntax has nothing to do with making the contrived example even more difficult to read?
- sgift 9y agoWhile certainly not the most readable example I'd like to know how much you've used rust? If you don't know the syntax of a language it often looks dreadful.
- fermienrico 9y agoYea, I admit, I haven't used Rust much except for following the MCU programming guide: https://japaric.github.io/copper/ https://japaric.github.io/copper/ Don't you think regardless of familiarity, Rust doesn't quite have a beautiful syntax? I am following this guide and it's like the visual equivalent of nails on a chalkboard. Perhaps it's subjective, what do you think?
- steveklabnik 9y agoIt's more complex than it needs to be; there's no reason to put a Box there, for example. (Even though the blog post is from April, it links to a SO question from 2015, where that Box was necessary, but as of Rust 1.4, a few months later, it is not. This info is in the comments of that answer.) It's also very unclear why they created an entire second Rc rather than just cloning the first one, as that's the point of Rc. Compare: fn my_trait_fn(t: &MyTrait) { t.trait_func(); } fn main() { let my_str = Rc::new(RefCell::new(MyStruct1)); my_trait_fn(&*my_str.borrow()); my_str.borrow().my_fn(); }
- moron4hire 9y agoThere are a lot of operations per line in that code. I would expect anyone interested in maintainability in any language to not write code like this. I don't think this is representative of Rust.
- mintplant 9y agoIn my opinion that's a contrived example. Why specify the type signature explicitly in the `let` line, instead of allowing the compiler to infer it? Why put a struct inside a `Box` (a heap-allocated pointer) and then the `Box` inside an `Rc` (a reference counted heap-allocated pointer)? Why go through the trouble of extracting the contents of this nesting doll of heap-allocated types, only to rewrap it back up in the same type structure before passing it to `my_trait_fn`? Why does `my_trait_fn` need to take ownership of an `Rc`? And if it really, really needs to, why not just use `clone`? Then you'd actually be taking advantage of the, uh, reference counting.
- steeleduncan 9y agoI am not sure as I haven't seen the entire codebase, but it is hard to understand why anyone would write rust like that. Heavy use of a construction like Rc<RefCell<Box<_>>> is often a sign that someone is trying to replicate something like c++'s std::shared_ptr<_> in rust rather than write code in a manner idiomatic to rust. Also, given that the article's second point results from a confusion between a memory slice and an array, it is difficult to see this as an informed discussion of rust.
- flohofwoe 9y agoIME it's the typical code after a few hours of "fighting the borrow checker" if you didn't plan your data layout very carefully with the borrow checker restrictions in mind.
- nine_k 9y agoThat is, it's a lack of experience and understanding. The same way, you can see a lot of casts and `instanceof` use in code of those who don't understand types / classes yet, or a lot of global variables in code of those who don't understand functions / procedures yet. If something in a language feels badly wrong, it's often a good idea to ask experienced people, or even the language's authors.
- vertex-four 9y agoThis code is just... wrong. On multiple levels. It's probably not doing what its author thinks it does. It also contains significant duplication where its entirely unnecessary. Suffice it to say that you should probably not judge languages based on examples written by people who don't understand the language.
- mi_lk 9y agoIf you have to grok the language to write concise code, you kind of prove the parent’s point isn’t it? That is, it’s easy for newcomers to write code that seems to be unreadable.
- vertex-four 9y agoMuch Python, especially written by newcomers, has very poor readability. I can't read most C++ at all, personally. It's easy to write unreadable code in most languages - especially when you're unfamiliar with the concepts and abstractions that language utilises.
- saghm 9y agoThis sounds like more of a learning curve thing than a code readability thing, IMO. Someone who doesn't know what they're doing can write unreadable code in virtually any language; the difference here is that Rust just takes more time to learn than something like Python or Ruby. While this is certainly something that can be improved (and lots of work is being done on this front!), I think the syntax itself is a bit of a strawman here.
- gaius 9y agoI am sure there is a way to come up with a better syntax without sacrificing the type safety aspects Well, this is the generic curly-braces-and-semicolons style. If you want to see nice clean yet type-safe syntax, just take a look at OCaml or Haskell.
- weberc2 9y agoWhat is meant by "type-safe syntax"? A syntax that permits type annotations? I will say I find Haskell and especially OCaml hard to read; I'm writing a pretty minimal language with a Haskell-esque syntax, but that's because it's easy to build a parser, not because I find it more readable. And the syntax will likely change when readability becomes more important than prototyping.
- evincarofautumn 9y agoI think the intent was “a language with nice clean syntax that is nonetheless type-safe”. A large portion of readability comes from familiarity. I find most Haskell code fairly easy to read—or at least, approximately as simple or complex syntactically as it is semantically. Other mainstream languages tend to be either much more complex syntactically than what they do semantically (“verbose”) or much simpler (“magic”).
- weberc2 9y agoThat makes sense, and I agree with your assessment of readability.
- fermienrico 9y agoWow, I just learned about Haskell syntax. I don't know the language but reading this makes me want to learn it: https://prajitr.github.io/quick-haskell-syntax/ https://prajitr.github.io/quick-haskell-syntax/ Beautiful. Absolutely beautiful.
- RX14 9y agoWhile its subjective, I really think ruby get this part right. When I look at ruby code I don't feel like my eyes hurt, I feel like the code is easy to skim, easy to find what I'm looking for, and there's the right balance between harsh symbols and softer alphanumerics. White space and indenting is also typically good on (good) ruby code. That's part of what I'm trying to keep alive in crystal.
- pdimitar 9y agoI agree on the Ruby syntax part but every single Ruby method can be monkey-patched to oblivion, and many gems do just that. What you see is very often NOT what you get with Ruby. I have looked quickly at Crystal and I liked it, really did. But it's too late for me now, I am in love with Elixir and already using it professionally, quite successfully too. Now that's a language with 90% WTF-free syntax (admittedly some of the pattern matching idioms and a few constructs can be confusing at times; very interested to see the builtin code formatter coming in version 1.6 though).
- MaulingMonkey 9y agolet my_str = Rc::new(RefCell::new(Box::new(MyStruct1))); Yep, even Rust has better syntax ;). The call is a little more gnarly. Keeping in mind that I don't really know rust, my understanding is that this is roughly the equivalent of trying to cast, in C++, shared_ptr<scoped_ptr<MyStruct1>> to shared_ptr<scoped_ptr<MyTrait>>. It's doable, but why the heck would you do it? The C++ equivalent might be: auto my_str = shared_ptr<unique_ptr<MyStruct1>>(new unique_ptr<MyStruct1>(new MyStruct1)); my_trait_fn(shared_ptr<unique_ptr<MyTrait>>(new unique_ptr<MyTrait>(new MyStruct1(**my_str)))); Which isn't much prettier. Why not instead: fn my_trait_fn_2(t: Rc<RefCell<MyTrait>>) { t.borrow_mut().trait_func(); } fn main() { let my_str2 = Rc::new(RefCell::new(MyStruct1)); my_trait_fn_2(my_str2.clone()); my_str2.borrow().my_fn(); } EDIT: Added a C++ example which actually compiles. EDIT x2: Fixed a double free.
- fermienrico 9y agoI admit, I don't know much about Rust. However, I know C/C++ a bit and this drives me nuts: int* foo; // This is a pointer. Understood. int *foo; // How is this allowed!???? Confusing! *foo = bar; // Now you're deferencing. WTF! In the second line, int foo confuses the hell out of me when I am scanning through pages of code. All of this mess could be solved by doing the pointer symbol after the variable name: int foo*; // This is a pointer
- qznc 9y agoThe philosophy in C/C++ is "you declare it how you use it". You declare it with `foo`, you use (assign to) it as `foo`. Likewise for arrays. You declare it `int foo[]` and you use it like `foo[i]`.
- lokedhs 9y agoBecause the * belongs to the variable, not the type. It's more clear with this example, which declares two variables at the same time: int *foo, bar; This declares foo as a pointer to int, and bar as an int. The following, written in the style you suggest, do the same thing but can be confusing, since a casual reader might think that this declares both foo and bar to be pointers to int: int* foo, bar; Someone else commented that C's declarations are designed to look the same as how they are used. This is true, and explains why arrays are declared the way they are, where the brackets come after the variable name: int foo[10]
- weberc2 9y agoThe syntactic differences between this Rust example and Java are pretty minimal. Types come after identifiers in Rust instead of before them, as in Java, and the namespace delimiter is `::` instead of `.`. The bits that are likely to hurt understanding pertain to memory management (borrowing, cloning, dereferencing, boxing, ref-counting, etc). Besides conflating memory-management with syntax, this could also be made more readable by writing idiomatic Rust: use type inference in your `let` statement, fix your indentation on the last line, etc.
- alkonaut 9y agoThat code is probably overly complicated for what it does. I do agree though that Rust, like C++ tends to hide the logic in implementation detail (pointer ownership, memory management code etc sometimes outweighs the actual business logic) Obviously these things aren't in Ruby or C# because it's implicit. But this is what systems programming is: you tell a computer both what to do, and exactly how to do it. I'd love for a systems programming language to look like Ruby or Haskell but I can't say I think it's possible. I once imagined a language should have two tiers where you can write the bulk (slow) of your code in a simpler gc'd language and the core parts in a lower level subset of the language. Today I do that with C# calling C for example, but the boundary too wide. Just having the same build system and package manager for example would be fantastic.