7 ms·
This was exceptionally well written, and it closely mirrors my experience as a python programmer slowly moving into Rust. Rust is awesome, but there are a few
by Fede_V 10y ago
This was exceptionally well written, and it closely mirrors my experience as a python programmer slowly moving into Rust.
Rust is awesome, but there are a few rough corners where you end up having to write really un-ergonomic code.
- blub 10y agoAfter reading this article my conclusion is that Rust is some kind of static-typed Perl with a focus on memory safety. The syntax is horrid. steveklabnik asked me once why I thought Rust is more complex than C++. Well, if one takes a look at any of the Rust snippets in this article without being deeply disturbed by the @'s the .'s, &'s and *'s, then we have a completely different idea about what it means to build a user-friendly and ergonomic programming language. Luckily I don't have to pick between Rust and Go, but if I had to I would just stick to Python and use type annotations after reading this.
- steveklabnik 10y agoIn a systems language, it's important to know if you're passing around things by value or reference. We follow the Python maxim here: explicit is better than implicit. This means &s to indicate where something is passed by a pointer, and * for dereferencing. This is the same as C++, we just do less magic coercions. We also tried to not make up too much syntax, and stick to "curly braces and semicolons" for most things. Even lifetime syntax isn't new, though OCaml uses it for a very different purpose than we do. (There also aren't any @s in there, unless I missed something? And . is the same as in any language: for method calls, and we made it _simpler_ than C or C++ here, by having . instead of . and ->.) I agree that you can make a language without many syntactical constructs, but Rust has other goals as well. Language design is hard! There's tons of tension between various choices.
- pjmlp 10y ago> This means &s to indicate where something is passed by a pointer, Having had a strong background doing system programming in Wirth based languages, this was something that I never liked in C, and was quite happy that C++ followed the same path as Algol derivatives.
- catnaroek 10y agoRust has both “essential verbosity” and “accidental verbosity”: (0) Lifetimes have to be represented in the language's syntax somehow, and this can't be avoided. Lifetime elision makes this a nonissue, anyway. (1) Rust's syntax for function declarations is horrendous. In a single place, it does two things that ought to be kept separate: state the type of the function and give names to the function's arguments. This by no means essential, e.g., Haskell does this much better. (2) The lack of global type inference really hurts when you write functions with syntactically large argument and/or return types. AFAICT, Rust's type system is Haskell's minus HKTs plus higher-ranked lifetimes. Most Rust code doesn't use anything higher-ranked, so Rust could have much more aggressive type inference. The only impediment is the Java-like syntax for method calls: you can't infer nominal object types from how objects are used. This is again purely accidental.
- masklinn 10y ago> This is again purely accidental. WRT 2 and 3, disabling/not doing global type inference was a wilful choice, Rust's designers specifically decided not to do that and to keep type inference local. Not splitting signatures out of function declarations comes from the same place, having removed GTI there was no point in making type signatures and function declarations independent, and it provides for uniform declaration when there's no function/method body ("abstract" trait methods, extern declarations).
- catnaroek 10y agoWillful or not, it makes the language more verbose.
- masklinn 10y agoI don't think it does, other syntactic decisions (e.g. C++/Java-style generic declarations, reference sigils) might, or at least make the language seem noisier, but with respect to function type declarations you'd get the exact same verbosity (in terms of tokens or characters), just split differently e.g. fn class(&mut self, s: &'a bstr) -> u16 versus class :: &mut, &'a bstr -> u16 fn class(self, s) all you get is repeating the function name.
- cousin_it 10y ago> In a systems language, it's important to know if you're passing around things by value or reference. I'm not sure that distinction should be always in your face though. Systems or no systems, you almost never want to pass a number by reference or pass an array by value.
- masklinn 10y ago> Systems or no systems, you almost never want to pass a number by reference It does occur for generic reference-based API though (e.g. many iterators) And you'd want to know whether you're passing a string or a vector by reference of value though, especially with affine types.
- cousin_it 10y agoWhy would you ever want to copy a string when passing it into a function?
- dbaupp 10y agoNote that "pass by value" isn't a copy in a language with affine types like Rust. I'm not sure passing everything by reference probably would gain much in a systems programming language in the vein of Rust: one would still need some syntactic method to distinguish between shared ("immutable") and non-shared ("mutable") references. Also, Rust-style static reasoning cares about things actually being pointers, to allow returning references safely. Lastly, being able to move things around is important too, it is a major part in Rust creating zero-overhead abstractions. I'm not sure exactly how MLKit works, but I glanced over https://github.com/melsman/mlkit/blob/master/doc/mlkit.pdf https://github.com/melsman/mlkit/blob/master/doc/mlkit.pdf and it seems to me that its regions are somewhat less static and that it doesn't attempt to handle concurrency. I could be wrong on both counts!
- cousin_it 10y agoI think you could have mut visible at the language level, but otherwise have all parameter passing look and feel like Java, without any pointer noise or implicit copying.
- atombender 10y agoRust is exceptionally noisy and terse. Easily worse than C++, in my opinion, which is known for being noisy. Two things bug me in particular. Rust eschews readability by erring on the side of terseness — fn instead of something like func, for example, might save some typing, but does contribute to making visually denser. The choice of snake_case instead of camelCase also adds to the noisiness, because there's no programmer font that aligns the underscore with the text baseline, so it's disruptive. Snake case is pretty aptly named; typographically, there's a lot more visual cohesiveness to camelcase. (As an aside, I never understood the lack of consistency here: the capitalized version of foo_bar should surely be Foo_Bar, not FooBar.) Python admittedly uses snakecase, but avoids looking too ramshackle by having otherwise very little noise. Compare something in Rust (from OP's repo): fn type_list<'a>(dex: &'a DexFile<'a>, off: u32, parse_cls_desc: bool) -> Vec<&'a bstr> { to Go: func typeList(dex *DexFile, off uint32, parseClassDesc bool) []BStr { I'm not the world's biggest fan of Go, but they did nail the readability aspect. Nim is another good example which does it even better. Of course, Go doesn't have lifetime annotations, but I can't understand who thought using ' was a great idea. I wish more time was spent on the visual aspect of a language. I'm really liking Reason, Facebook's new syntax for OCaml, which shows that a language can be made fresh and friendlier through aesthetics alone. Elixir is cleaner than Erlang, but I think it went too far in emulating Ruby; instead of very simply aligning "def" and "end", for example, you now have "do ... end" everywhere). Edit: Typo.
- pcwalton 10y agoYour criticism comes down to wanting a different abbreviation for "function" and wanting camel case instead of snake case. I would suggest that it's not that we didn't spend time on the "visual aspect" of a language: rather it's that we made choices that were not your preference. We had to pick something and disappoint somebody; I apologize that that person was you. > Compare something in Rust (from OP's repo): As with so many of these comparisons, you're effectively comparing a language with GC to a language with safe manual memory management, and misinterpreting this as evidence of bad syntactic choices. (Same with Nim: Nim has GC.) Look at the function signature when you remove the lifetime annotations: fn type_list(dex: &DexFile, off: u32, parse_class_desc: bool) -> Vec<bstr> When you do that, there's very little difference between that and Go. > Of course, Go doesn't have lifetime annotations, but I understand who thought using ' was a great idea. Me. It was chosen because it's very visually lightweight.
- unethical_ban 10y agoHi Steve, I've been working on learning Rust from a beginner perspective (know some Python, never written a program with more than 500 loc). However, I do know what pointers are, and get the concept of stack and heap. My comment is this: It is not immediately apparent that when using &var syntax, Rust is "auto-dereferencing" the variable to get the value. It took a moment to wrap my head around why println!("{}", &x); //this can show "5". Rust's ownership/borrowing mechanism, where variables basically "die" if you don't pass by reference, is confusing until I read the FAQ and those three chapters in the Book a few times. What I think the Book, or some other reference, needs is a "best practices for newbies" guide. Kind of like how we learn the atom is the smallest possible object in high school, I think it would be ok for a doc like this to tell newbies "You almost never want to pass a variable by value". It would help keep people from fighting the borrow checker. Let me know if I'm crazy.
- steveklabnik 10y agoHey hey! So, this specific example has nothing to do with auto dereferencing in the language. It's that Display, implemented for &T, displays the value instead of the reference. This is what you want 99% of the time, so Rust is trying to Do The Right Thing here. If you want to print out the numeric version of the pointer, {:p} will do it. It's a bit tough to document specific implementations at the moment, so it's certainly not as clear as it could be. you're right. ( For reference, this implementation is here: https://github.com/rust-lang/rust/blob/master/src/libcore/fmt/mod.rs#L1348-L1363 https://github.com/rust-lang/rust/blob/master/src/libcore/fm... note that this is in a macro to do this for all of the traits at once...) I'm actually re-writing the book right now, and one of the things I'm focusing hard on is the stuff around ownership and borrowing. The current chapters are too abstract, the new ones are focused on String/&str. You can find that here: http://rust-lang.github.io/book/ch04-00-understanding-ownership.html http://rust-lang.github.io/book/ch04-00-understanding-owners... > "You almost never want to pass a variable by value". So, the issue with advice like this is that it's not always true: you almost never want to pass some things by value, but Copy types, for example, you almost always want to pass by value. It's tricky! That said, it's true that learning Rust can be tricky; we're trying to focus on improving that in the coming year. So keep it coming! This kind of feedback is very helpful, thank you.
- jpfed 10y agoIf you're deeply disturbed by all the @s then you formed your opinion more than a year ago.
- alexnewman 10y agoSorry mr blub, but you discredited yourself by that response. You might find it great to take another look at rust.