diff options
Diffstat (limited to 'src/blog')
| -rw-r--r-- | src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md b/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md new file mode 100644 index 0000000..e17f89d --- /dev/null +++ b/src/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md | |||
| @@ -0,0 +1,332 @@ | |||
| 1 | # Cargo culture shock 🦀 | ||
| 2 | |||
| 3 | After a long adventure | ||
| 4 | [porting my cube solver to the web](../2025-06-06-webdev), I decided to | ||
| 5 | try out something completely different, like learning a new language. | ||
| 6 | Rust is one on my to-do list, and it has been there for more than 10 | ||
| 7 | years - I remember reading about it in my first year in university, | ||
| 8 | so it must have been 2013 or early 2014. | ||
| 9 | |||
| 10 | So I started by quickly reading through the first few chapters of | ||
| 11 | [the book](https://doc.rust-lang.org/book/) to get an idea of the basic | ||
| 12 | syntax. Before I move on to implementing something, I thought I could | ||
| 13 | share my very early impression of the language and the tooling around it. | ||
| 14 | |||
| 15 | *Note: this is a relaxed write-up and I have a very superficial | ||
| 16 | understanding of the topic. I am going to use strong words for the things | ||
| 17 | that I did not like, but there are many things I like about Rust so | ||
| 18 | far. In fact, my first impression of the language, the documentation and | ||
| 19 | the tools is very positive! Keep this in mind while reading this post.* | ||
| 20 | |||
| 21 | ## What is Rust about? | ||
| 22 | |||
| 23 | Before getting started with the Rust book, my impression was that Rust | ||
| 24 | was mostly about *safety* as in *memory safety*, and that it achieved | ||
| 25 | this by enforcing strict rules, leading to more correct programs overall. | ||
| 26 | |||
| 27 | But [the foreword](https://doc.rust-lang.org/book/foreword.html) says "the | ||
| 28 | Rust programming language is fundamentally about *empowerment*". Uh, that's | ||
| 29 | weird. I was expecting "something something *safety*". And actually, | ||
| 30 | I was hoping for "something something *correctness*". Definitely not | ||
| 31 | "something something *empowerment*". *Empowerment* sounds like one of | ||
| 32 | those meaningless words that managers use when they have nothing to say. | ||
| 33 | |||
| 34 | Anyway, empowerement it is. Let's move on to actually using the thing. | ||
| 35 | |||
| 36 | ## Installation | ||
| 37 | |||
| 38 | The officially endorsed way of installing Rust is the following: | ||
| 39 | |||
| 40 | ``` | ||
| 41 | curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh | ||
| 42 | ``` | ||
| 43 | |||
| 44 | The horror, the horror! Is this really what they suggest? The very thing | ||
| 45 | that *everyone* told you not to do in nerd forums until a few years ago, | ||
| 46 | even worse than copy-pasting commands from the internet, is now THE | ||
| 47 | suggested way of installing software? Seriously, piping a random web | ||
| 48 | page into `sh`? | ||
| 49 | |||
| 50 | I don't know what this script does, is it going to download even more | ||
| 51 | random shit from the internet? (Answer: yes, it is!) Is it going to fuck | ||
| 52 | up my environment by puking configuration lines into my .bashrc? (Answer: | ||
| 53 | yes, it is!) | ||
| 54 | |||
| 55 | But ok, what do I know. Maybe I am just leaving in the past, I should | ||
| 56 | embrace the future, because package managers and `make && make install` | ||
| 57 | *are so 1999, man*. So I took a deep breath an run the command. | ||
| 58 | Luckily the script kindly asked "do you want me to fuck up your | ||
| 59 | environment? [default: Yes]". I may be paraphrasing this, it probably | ||
| 60 | mentioned `.bash_rc` and `.profile`. Anyway, you can easily say "no" | ||
| 61 | and skip this fuckery. And then manually update a couple of environment | ||
| 62 | variables in your shell configuration file, which is what the script was | ||
| 63 | trying to do. I guess developers today are not expected to know how to | ||
| 64 | do this. | ||
| 65 | |||
| 66 | The installation was quick and everything went fine. Moving on! | ||
| 67 | |||
| 68 | ## Cargo cult | ||
| 69 | |||
| 70 | [Cargo](https://doc.rust-lang.org/book/ch01-03-hello-cargo.html) is | ||
| 71 | Rust's build system and all-in-one tool. It can create a new project | ||
| 72 | with a default folder structure, compile code (running `rustc` under | ||
| 73 | the hood`), run tests, and more. Oh and it is also a *package manager*, | ||
| 74 | which apparently is something modern programming languages decided they | ||
| 75 | needed - more on that later. | ||
| 76 | |||
| 77 | I don't always like these kind of tools, becase they tend to do a lot of | ||
| 78 | things that you don't understand with the files in the current directory. | ||
| 79 | Like vomiting generated files that you are supposed to check into your | ||
| 80 | [VCS](https://en.wikipedia.org/wiki/Version_control) - which kinda | ||
| 81 | defeats the purpose of *generating* files in the first place, doesn't it? | ||
| 82 | |||
| 83 | Luckily, Cargo does not do too much of this. It generates an `src` folder | ||
| 84 | with a simple "hello world" program, a pretty minimal `Cargo.toml` | ||
| 85 | configuration file, and it initializes a git repo. All reasonable, | ||
| 86 | and most importantly easy to understand. | ||
| 87 | |||
| 88 | On the first run of `cargo build` it also generates a `target` folder | ||
| 89 | for the build artifacts (already in `.gitignore`) and it generates | ||
| 90 | only one of those vomit files that I am supposed to check into git | ||
| 91 | (`Cargo.lock`), which I promptly added to .gitignore. Apparently it is | ||
| 92 | mainly about dependencies, and I'd rather not use any for now. So all | ||
| 93 | in all I am satisfied with this process. | ||
| 94 | |||
| 95 | About dependencies, you can specify some packages, or *crates*, that your | ||
| 96 | projects depends on. These *crates* are going to be downloaded as they are | ||
| 97 | needed from [crates.io](https://crates.io/). You can of course specify | ||
| 98 | some constraints on the version for each specific crate, like "at least | ||
| 99 | 2.0" or "at least 0.8.5, but less than 0.9.0". Here is where `Cargo.lock` | ||
| 100 | comes into play: if there is more than one version available for a given | ||
| 101 | crate that satisfies the contraints you impose in `Cargo.toml`, the first | ||
| 102 | time a specific version is used it gets written to `Cargo.lock`, so that | ||
| 103 | you will keep using that version even if a new one becomes available. | ||
| 104 | You are supposed to check in this file to git so other developers working | ||
| 105 | on the same project will use exactly the same version of each dependency. | ||
| 106 | |||
| 107 | I can't quite understand this. If I don't want to update to a later | ||
| 108 | (minor) version of the dependency, I can already specify this in | ||
| 109 | `Cargo.toml`. If instead I impose more relaxed requirements, then it | ||
| 110 | means that any version satisfying those requirements is fine, and I | ||
| 111 | in this case *I do want* to try different versions within the allowed | ||
| 112 | range, to make sure that my assumptions are correct. So I am confused | ||
| 113 | about why this Cargo.lock business is needed at all. But I guess if | ||
| 114 | the time ever comes that I need to include dependencies in my project - | ||
| 115 | probably in the far future, *right?* - then I can just specify an exact | ||
| 116 | version and happily ignore `Cargo.lock`. | ||
| 117 | |||
| 118 | Anyway, coming from a mostly C and C++ background where there is no | ||
| 119 | standard way of including dependencies, all of this is certainly quite | ||
| 120 | interesting. | ||
| 121 | |||
| 122 | ## Dependencies? | ||
| 123 | |||
| 124 | Chapter 2 contains a simple code example. | ||
| 125 | [This part](https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html#generating-a-secret-number) | ||
| 126 | caught my attention: | ||
| 127 | |||
| 128 | "Rust doesn't yet include random number functionality in its standard library." | ||
| 129 | |||
| 130 | Uh, ok. And then people complain about C having a small standard library. | ||
| 131 | It then continues: | ||
| 132 | |||
| 133 | "However, the Rust team does provide a | ||
| 134 | [`rand` crate](https://crates.io/crates/rand) with said functionality." | ||
| 135 | |||
| 136 | Ok, but... if this is provided by the Rust team, why not including it in | ||
| 137 | the standard library? Maybe it is still in beta, Rust is not a "finished" | ||
| 138 | project as far as I understand. I would think 15 years is enough to ship | ||
| 139 | a random number generator, though. | ||
| 140 | |||
| 141 | The tutorial tell us to add `rand` to our dependencies in `Cargo.toml`. | ||
| 142 | Simple enough. But then, if we launch `cargo build` again: | ||
| 143 | |||
| 144 | ``` | ||
| 145 | cargo build | ||
| 146 | Updating crates.io index | ||
| 147 | Locking 15 packages to latest Rust 1.85.0 compatible versions | ||
| 148 | Adding rand v0.8.5 (available: v0.9.0) | ||
| 149 | Compiling proc-macro2 v1.0.93 | ||
| 150 | Compiling unicode-ident v1.0.17 | ||
| 151 | Compiling libc v0.2.170 | ||
| 152 | Compiling cfg-if v1.0.0 | ||
| 153 | Compiling byteorder v1.5.0 | ||
| 154 | Compiling getrandom v0.2.15 | ||
| 155 | Compiling rand_core v0.6.4 | ||
| 156 | Compiling quote v1.0.38 | ||
| 157 | Compiling syn v2.0.98 | ||
| 158 | Compiling zerocopy-derive v0.7.35 | ||
| 159 | Compiling zerocopy v0.7.35 | ||
| 160 | Compiling ppv-lite86 v0.2.20 | ||
| 161 | Compiling rand_chacha v0.3.1 | ||
| 162 | Compiling rand v0.8.5 | ||
| 163 | Compiling guessing_game v0.1.0 (file:///projects/guessing_game) | ||
| 164 | Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.48s | ||
| 165 | ``` | ||
| 166 | |||
| 167 | Woho wait a minute. I have only added one dependency! Just a random | ||
| 168 | number generator! Why is it building 15 packages now? Where does all | ||
| 169 | of this come from? Surely these are not dependencies dragged in by | ||
| 170 | the `rand` crate, *right?* | ||
| 171 | |||
| 172 | Well, apparently they are. If we want to generate a random number, we | ||
| 173 | need 15 external packages. I would imagine this makes Rust completely | ||
| 174 | unusable in a professional setting, because nobody would want to audit | ||
| 175 | 15 separate crates just to include a random number generator. And no | ||
| 176 | professional programmer would include dependencies in a serious project | ||
| 177 | without first auditing them, *right?* *RIGHT?!* | ||
| 178 | |||
| 179 | I mean, each dependency introduces an extra liability in your project; | ||
| 180 | it is code that you can't control and you never know when it is going to | ||
| 181 | break. And this is why serious programmers, select dependencies carefully | ||
| 182 | and only use them when absolutely necessary... *right?* | ||
| 183 | |||
| 184 | Ahah, of course they do not. Remember the | ||
| 185 | [letf-pad incident](https://en.wikipedia.org/wiki/Npm_left-pad_incident)? | ||
| 186 | Apparently Cargo is of the same breed as npm. And then we wonder why | ||
| 187 | the software industry is such a shitshow. *Just keep building on top | ||
| 188 | of the house of cards, man. It's going to be fine, man, don't bother | ||
| 189 | implementing low-level stuff, that's hard. Just trust random people on | ||
| 190 | the internet and ship their code, man.* | ||
| 191 | |||
| 192 | Bleah. | ||
| 193 | |||
| 194 | ## The Rust language | ||
| 195 | |||
| 196 | So far I talked mostly about the tooling around Rust, but I said nothing | ||
| 197 | about the language itself. I want to write some code before making a | ||
| 198 | more informed opinion about it, but the first impression is that it is | ||
| 199 | really nice! | ||
| 200 | |||
| 201 | I love the type system, especially | ||
| 202 | [enums](https://doc.rust-lang.org/book/ch06-00-enums.html) | ||
| 203 | and `match`. I like that this and stuff like `Option<T>` are | ||
| 204 | first-class citizens; you can do something similar in C++ with | ||
| 205 | [`std::variant`](https://doc.rust-lang.org/book/ch06-00-enums.html) and | ||
| 206 | [`std::optional`](https://en.cppreference.com/w/cpp/utility/optional.html), | ||
| 207 | but the syntax quickly gets messy. I guess this shows the advantage of | ||
| 208 | making a new language from scratch instead of being forced to live with | ||
| 209 | decades-old syntax for backwards compatibility. | ||
| 210 | |||
| 211 | I especially like this mechanism - Rust's enums, or `std::variant`, or | ||
| 212 | tagged unions, whatever you like to call them - for error handling. I find | ||
| 213 | them a better solution than exception, because they enforce correctness: | ||
| 214 | if your function can fail, your coding must handle it; you can't just | ||
| 215 | let an error message bubble up from the depths of Hell, wreak havoc in | ||
| 216 | your control flow and face the user with an "Object reference not set | ||
| 217 | to an instance of an object". I think it is great that Rust endorses | ||
| 218 | this more thorough way of error handling from the start. | ||
| 219 | |||
| 220 | On the less-nice side of things, apparently | ||
| 221 | [integer overflow](https://doc.rust-lang.org/book/ch03-02-data-types.html?highlight=overflow#integer-overflow) | ||
| 222 | is still an issue. Eh. At least when compiling in debug mode overflows | ||
| 223 | are caught, which is nice. But in release mode the program is | ||
| 224 | going to "panic", which I was not expecting. I mistakenly thought | ||
| 225 | this was one of the issues that Rust was solving at compile time, | ||
| 226 | or maybe even at the level of language specification. | ||
| 227 | |||
| 228 | This brings me to another problem that I thought Rust would solve, | ||
| 229 | but it does not: accessing a out-of-bound index of an array also leads | ||
| 230 | to a panic. I was expecting "don't allow indexes out of bound" to be | ||
| 231 | included in Rust's compile-time checks, somehow. For example in Ada you | ||
| 232 | can declare an array to accept only values of a specific | ||
| 233 | [range type](https://en.wikibooks.org/wiki/Ada_Programming/Types/range), | ||
| 234 | so that out-of-bound errors are completely eliminated. Apparently a | ||
| 235 | "panic" is memory safe behavior, but it is definitely not *correct* | ||
| 236 | behavior. I guess this was a big misunderstanding from my side: memory | ||
| 237 | *safety* does not mean memory *correctness* - Rust still allows you to | ||
| 238 | make mistakes related to memory. | ||
| 239 | |||
| 240 | ## Documentation | ||
| 241 | |||
| 242 | A quick note on the documentation: it is very nice. The book is well | ||
| 243 | written, and I am enjoying reading through it, even though it is very | ||
| 244 | basic for me. It is also available offline - at least when installing | ||
| 245 | rust via `rustup` - which is something I always appreciate. | ||
| 246 | |||
| 247 | The compiler error messages, which I consider to be part of the | ||
| 248 | documentation as well, are simply amazing. Not only they are usually very | ||
| 249 | precise, not only they often suggest a fix, but they also point you out to | ||
| 250 | some piece of documentation so that you can learn why your code is wrong. | ||
| 251 | For example, if you try to compile this code with `rustc filename.rs`: | ||
| 252 | |||
| 253 | ``` | ||
| 254 | fn main() { | ||
| 255 | println("Hello, world!"); | ||
| 256 | } | ||
| 257 | ``` | ||
| 258 | |||
| 259 | You get: | ||
| 260 | |||
| 261 | ``` | ||
| 262 | error[E0423]: expected function, found macro `println` | ||
| 263 | --> hello.rs:2:2 | ||
| 264 | | | ||
| 265 | 2 | println("Hello, world!"); | ||
| 266 | | ^^^^^^^ not a function | ||
| 267 | | | ||
| 268 | help: use `!` to invoke the macro | ||
| 269 | | | ||
| 270 | 2 | println!("Hello, world!"); | ||
| 271 | | + | ||
| 272 | |||
| 273 | error: aborting due to 1 previous error | ||
| 274 | |||
| 275 | For more information about this error, try `rustc --explain E0423`. | ||
| 276 | ``` | ||
| 277 | |||
| 278 | And if you run `rustc --explain E0423` as suggested: | ||
| 279 | |||
| 280 | ``` | ||
| 281 | An identifier was used like a function name or a value was expected and the identifier exists but it belongs to a different namespace. | ||
| 282 | |||
| 283 | Erroneous code example: | ||
| 284 | |||
| 285 | struct Foo { a: bool }; | ||
| 286 | |||
| 287 | let f = Foo(); | ||
| 288 | // error: expected function, tuple struct or tuple variant, found `Foo` | ||
| 289 | // `Foo` is a struct name, but this expression uses it like a function name | ||
| 290 | |||
| 291 | Please verify you didn't misspell the name of what you actually wanted to use here. Example: | ||
| 292 | |||
| 293 | fn Foo() -> u32 { 0 } | ||
| 294 | |||
| 295 | let f = Foo(); // ok! | ||
| 296 | |||
| 297 | It is common to forget the trailing ! on macro invocations, which would also yield this error: | ||
| 298 | |||
| 299 | println(""); | ||
| 300 | // error: expected function, tuple struct or tuple variant, | ||
| 301 | // found macro `println` | ||
| 302 | // did you mean `println!(...)`? (notice the trailing `!`) | ||
| 303 | |||
| 304 | Another case where this error is emitted is when a value is expected, but something else is found: | ||
| 305 | |||
| 306 | pub mod a { | ||
| 307 | pub const I: i32 = 1; | ||
| 308 | } | ||
| 309 | |||
| 310 | fn h1() -> i32 { | ||
| 311 | a.I | ||
| 312 | //~^ ERROR expected value, found module `a` | ||
| 313 | // did you mean `a::I`? | ||
| 314 | } | ||
| 315 | ``` | ||
| 316 | |||
| 317 | I would not be surprised if the main reason why Rustaceans are so | ||
| 318 | enthusiastic about the language was actually how nice `rustc` is. | ||
| 319 | I expect working with this tool will be very pleasant. | ||
| 320 | |||
| 321 | ## Moving on! | ||
| 322 | |||
| 323 | Apart from the | ||
| 324 | [culture shock](https://en.wikipedia.org/wiki/Culture_shock) of the | ||
| 325 | installation process and the dependency management, my first impression | ||
| 326 | of Rust is quite positive. But as I said, I am just getting started | ||
| 327 | and my judgement is very superficial. I do want to write some small | ||
| 328 | project in Rust, and I think I'll start from re-writing a simple [math | ||
| 329 | library for modular arithmetic](../2025-01-21-taming-cpp-templates) | ||
| 330 | that I wrote in C++ some time ago. | ||
| 331 | |||
| 332 | Stay tuned for more 🦀 | ||
