No kidding. Effective Modern C++ was what got me to actually start learning Rust.
That being said I feel Rust has a lot of paper cuts that slow people down too. Perhaps the silver lining is that they are more like barricades than minefields.
Rust would be so much easier for me if it didn't feel like every. single. keyword. was different than my daily drivers, Python and C---from type names to standard library functions, containers to punctuation. Even "void" isn't safe (hehe pun) from this keyword purge.
Beginners, count your blessings. You don't need to unlearn other languages before learning Rust.
I sincerely believe the only keywords common to Rust and C are a few control flow keywords (for, if, while; but not switch, goto, or do-while) and "bool"---the latter only because of C99 and stdbool.h
readability is so underrated.
I think readability is a major part why java was hugely successful: C programmers could read java code and immediately understand what the code did and able to make minor modifications to the code.
This is very difficult with Rust code. Rust "learned" too much from Perl in this regard.
Do you think so ? Maybe I've been immersed in Rust for too long but it doesn't seem difficult to read to me.
Here's Barry Revzin, a C++ programmer, explaining what he wants with a Rust example
https://brevzin.github.io/c++/2020/12/01/tag-invoke/#this-is...
Now like I said, I'm a Rust programmer, so of course I understand PartialEq's definition there, but, is it really hard to see what's going on as a C programmer? There's a lot of magic you don't have, you don't have traits, or references, or the Self type or the self keyword, or really any of the mechanics here, and yet it seems pretty obvious what's going on, doesn't it?
I literally don't remember any unpleasant surprises of this form when learning Rust. Actually mostly the opposite, especially for != I thought, from experience with other languages like Java, C++ or Python, OK obviously at some point the other shoe drops and they show me that I need a deep comparison feature. Nope, Rust's comparison operators are for comparing things, it isn't used to ask about some surface programmer implementation detail like the address of an object in memory unless you specifically ask for that. If Goose implements PartialEq, so that we can ask if one Goose is the same as another Goose, then HashSet also implements PartialEq, so we can ask if this set of geese is the same as another, and HashMap likewise, so we can ask if these two maps from geese to strings are mapping the same geese to the same strings.
I know ripgrep is written in Rust and works really well and "does one thing", so I'll go to its repo ( https://github.com/BurntSushi/ripgrep ) and read through a bit. First, I'm looking for a "src" folder, but that does not exist, so right off the bat I am a little uncomfortable. Nothing in the root folder looks like it would be the source. What are "complete" and "scripts" and "pkg" folders? Because those (in that order) would be what I check.
"complete" looks like command line completion. The "scripts" directory has one file, "copy-examples". "pkg" contains folders "brew" and "windows". Still lost...
I thought "crates" was like modules for Python, so I think that would only contain dependencies and stay out of there.
Finally, I relent and open "build.rs" which I suspect is something like "build.ninja" and I can figure out which node has source files. Stupid me. "build.rs" IS the source file.
Oh. No, actually, it's not all of ripgrep, in fact. It's the tip of the iceberg. The source is in crates/core/app.rs, and build does manpage compilation, registers shell completions, etc.
So, line one. I don't know what the return value App<'static, 'static> is. I understand it's an App object, and the tick (no idea what it's called; this is starting to feel like a fracture mechanics lecture) I know has to do with ownership. It's tough to see how there are two subtypes in App<> when the assignment of app has ten functions that assign attributes. I can certainly READ what is being assigned to the App object. I wouldn't be able to WRITE any of this so far. Unimportant, it's not the meat and potatoes of ripgrep.
Now I'm scrolling, looking for something cool to analyze and run across Vec<&'static str>. Okay, I thought tick was ownership, but I also thought ampersand was ownership-related. Maybe one is mutability? (This is like knowing how to play music really well but now learning a totally foreign instrument.)
Skimming, it looks like this whole file is the argc/argv parser. I'll look for something interesting in main.rs, search.rs, and subject.rs (the last because it's an unusual name).
struct Config {
strip_dot_prefix: bool,
}
Well. I can read that! Next line... impl Default for Config {
...
...not that. It's probably something like a class method.As I'm reading through, it feels like a LOT of kicking the can down the road and verbosity. There's a function binary_detection_explicit(self, detection) that just assigns detection to self.config.binary_explicit and returns self. binary_detection_implicit() does basically the same with a different assignment member. getters and setters, roadblocking readability since the dawn of computer science... (a few lines later, the function has_match() returns the value of has_match. ugh. This is why I prefer crudely written C to textbook C++.)
I see .unwrap() in a line. This tripped me up during the tutorial; still no idea what it does. Sure I can look it up, but there are a hundred other terms to learn: unwind, map, Result (OH! The two App return types are probably Ok and Err ... maybe?), convert, concat, const, continue, ...
subject.rs, btw, can probably be written in three lines of really dense Python or ten lines of really well-written Python with a few other lines of Doxygen. Instead, it's about 90 lines of Rust with another 50 lines of comments.
Finally, I find something noteworthy.
fn search(...) ...
fn iter(...) ...
for subject in subjects {
searched = true;
let search_result = match searcher.search(&subject) {
....
This is all extremely readable and just feels like a standard queue in any language. But then there are lines like: if let Some(ref mut stats) = stats {
*stats += search_result.stats().unwrap();
}
I give up. Are you assigning the variable stats to itself? by reference but mutable? If Rust doesn't have pointers, what is *stats? unwrap() is no longer the most confusing part of this.-----
Postface, I'm originally a mechanical engineer who eventually got roped into writing driver software, so C is my comfort zone and I don't need anything much more complicated than "set bits here, read registers, use a 40-year-old communication protocol, handle errors".
There's simply no easy analog between Rust and Python ... or Rust and C ... or Rust and SQL ... or Rust and PRACTICE ... or Rust and any other language I've learned. I understand a lot of the CWE Top 25 software errors are mitigated by Rust and it's not just a theoretically correct but unusable language, but there hasn't been a strong reason to learn it, and the learning curve is made steeper by preconceived notions of every keyword.
-----
I forgot to compare with grep.c
Extremely readable. I know right away which part is args, where memory allocation happens, where file handling occurs, and then the "hard part"---where the magic happens---is the last page on my monitor. I can just stare at that and mentally deconstruct until pieces fall into place... Look up regcomp()? check. Look up grep_tree()? check. Read the kernel.org discussion on why grep_tree() was written and how it relates to ensure_full_index(), which explains some of the other variables.
The whole thing sits nicely in a few pages with terse but clear comments (unlike this post) and looks familiar even though I've never seen the source before.
-----
Last edit. I tried just now to refresh on .unwrap(). https://doc.rust-lang.org/rust-by-example/error/option_unwra...
Only because of the comments did I realize that calling drink.unwrap() will panic (throw an error?) if that argument is empty. So, unwrap is just the "if (... == nullptr) { return ...; }" of Rust, it seems.
But it's somehow an alternative to .expect() and has its own alternatives, unwrap_or/unwrap_or_else/unwrap_or_default, and it also seems optional. And my time in the rabbit hole is over; my children need to go to bed.