cargo-culture-shock.md (14437B)
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 living 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#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 329 [math library for modular arithmetic](../2025-01-21-taming-cpp-templates/#constraints-and-concepts) 330 that I wrote in C++ some time ago. 331 332 Stay tuned for more 🦀