sebastiano.tronto.net

Source files and build scripts for my personal website
git clone https://git.tronto.net/sebastiano.tronto.net
Download | Log | Files | Refs | README

commit 86059c69cdd5fc46fee8cb4a0c859d211f4a2058
parent ccc759c09c0738efce3d3f6f063b98fc55c27802
Author: Sebastiano Tronto <sebastiano@tronto.net>
Date:   Fri, 13 Jun 2025 15:11:45 +0200

Blog post

Diffstat:
Asrc/blog/2025-06-13-cargo-culture-shock/cargo-culture-shock.md | 332+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file 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 @@ -0,0 +1,332 @@ +# Cargo culture shock 🦀 + +After a long adventure +[porting my cube solver to the web](../2025-06-06-webdev), I decided to +try out something completely different, like learning a new language. +Rust is one on my to-do list, and it has been there for more than 10 +years - I remember reading about it in my first year in university, +so it must have been 2013 or early 2014. + +So I started by quickly reading through the first few chapters of +[the book](https://doc.rust-lang.org/book/) to get an idea of the basic +syntax. Before I move on to implementing something, I thought I could +share my very early impression of the language and the tooling around it. + +*Note: this is a relaxed write-up and I have a very superficial +understanding of the topic. I am going to use strong words for the things +that I did not like, but there are many things I like about Rust so +far. In fact, my first impression of the language, the documentation and +the tools is very positive! Keep this in mind while reading this post.* + +## What is Rust about? + +Before getting started with the Rust book, my impression was that Rust +was mostly about *safety* as in *memory safety*, and that it achieved +this by enforcing strict rules, leading to more correct programs overall. + +But [the foreword](https://doc.rust-lang.org/book/foreword.html) says "the +Rust programming language is fundamentally about *empowerment*". Uh, that's +weird. I was expecting "something something *safety*". And actually, +I was hoping for "something something *correctness*". Definitely not +"something something *empowerment*". *Empowerment* sounds like one of +those meaningless words that managers use when they have nothing to say. + +Anyway, empowerement it is. Let's move on to actually using the thing. + +## Installation + +The officially endorsed way of installing Rust is the following: + +``` +curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh +``` + +The horror, the horror! Is this really what they suggest? The very thing +that *everyone* told you not to do in nerd forums until a few years ago, +even worse than copy-pasting commands from the internet, is now THE +suggested way of installing software? Seriously, piping a random web +page into `sh`? + +I don't know what this script does, is it going to download even more +random shit from the internet? (Answer: yes, it is!) Is it going to fuck +up my environment by puking configuration lines into my .bashrc? (Answer: +yes, it is!) + +But ok, what do I know. Maybe I am just leaving in the past, I should +embrace the future, because package managers and `make && make install` +*are so 1999, man*. So I took a deep breath an run the command. +Luckily the script kindly asked "do you want me to fuck up your +environment? [default: Yes]". I may be paraphrasing this, it probably +mentioned `.bash_rc` and `.profile`. Anyway, you can easily say "no" +and skip this fuckery. And then manually update a couple of environment +variables in your shell configuration file, which is what the script was +trying to do. I guess developers today are not expected to know how to +do this. + +The installation was quick and everything went fine. Moving on! + +## Cargo cult + +[Cargo](https://doc.rust-lang.org/book/ch01-03-hello-cargo.html) is +Rust's build system and all-in-one tool. It can create a new project +with a default folder structure, compile code (running `rustc` under +the hood`), run tests, and more. Oh and it is also a *package manager*, +which apparently is something modern programming languages decided they +needed - more on that later. + +I don't always like these kind of tools, becase they tend to do a lot of +things that you don't understand with the files in the current directory. +Like vomiting generated files that you are supposed to check into your +[VCS](https://en.wikipedia.org/wiki/Version_control) - which kinda +defeats the purpose of *generating* files in the first place, doesn't it? + +Luckily, Cargo does not do too much of this. It generates an `src` folder +with a simple "hello world" program, a pretty minimal `Cargo.toml` +configuration file, and it initializes a git repo. All reasonable, +and most importantly easy to understand. + +On the first run of `cargo build` it also generates a `target` folder +for the build artifacts (already in `.gitignore`) and it generates +only one of those vomit files that I am supposed to check into git +(`Cargo.lock`), which I promptly added to .gitignore. Apparently it is +mainly about dependencies, and I'd rather not use any for now. So all +in all I am satisfied with this process. + +About dependencies, you can specify some packages, or *crates*, that your +projects depends on. These *crates* are going to be downloaded as they are +needed from [crates.io](https://crates.io/). You can of course specify +some constraints on the version for each specific crate, like "at least +2.0" or "at least 0.8.5, but less than 0.9.0". Here is where `Cargo.lock` +comes into play: if there is more than one version available for a given +crate that satisfies the contraints you impose in `Cargo.toml`, the first +time a specific version is used it gets written to `Cargo.lock`, so that +you will keep using that version even if a new one becomes available. +You are supposed to check in this file to git so other developers working +on the same project will use exactly the same version of each dependency. + +I can't quite understand this. If I don't want to update to a later +(minor) version of the dependency, I can already specify this in +`Cargo.toml`. If instead I impose more relaxed requirements, then it +means that any version satisfying those requirements is fine, and I +in this case *I do want* to try different versions within the allowed +range, to make sure that my assumptions are correct. So I am confused +about why this Cargo.lock business is needed at all. But I guess if +the time ever comes that I need to include dependencies in my project - +probably in the far future, *right?* - then I can just specify an exact +version and happily ignore `Cargo.lock`. + +Anyway, coming from a mostly C and C++ background where there is no +standard way of including dependencies, all of this is certainly quite +interesting. + +## Dependencies? + +Chapter 2 contains a simple code example. +[This part](https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html#generating-a-secret-number) +caught my attention: + +"Rust doesn't yet include random number functionality in its standard library." + +Uh, ok. And then people complain about C having a small standard library. +It then continues: + +"However, the Rust team does provide a +[`rand` crate](https://crates.io/crates/rand) with said functionality." + +Ok, but... if this is provided by the Rust team, why not including it in +the standard library? Maybe it is still in beta, Rust is not a "finished" +project as far as I understand. I would think 15 years is enough to ship +a random number generator, though. + +The tutorial tell us to add `rand` to our dependencies in `Cargo.toml`. +Simple enough. But then, if we launch `cargo build` again: + +``` +cargo build + Updating crates.io index + Locking 15 packages to latest Rust 1.85.0 compatible versions + Adding rand v0.8.5 (available: v0.9.0) + Compiling proc-macro2 v1.0.93 + Compiling unicode-ident v1.0.17 + Compiling libc v0.2.170 + Compiling cfg-if v1.0.0 + Compiling byteorder v1.5.0 + Compiling getrandom v0.2.15 + Compiling rand_core v0.6.4 + Compiling quote v1.0.38 + Compiling syn v2.0.98 + Compiling zerocopy-derive v0.7.35 + Compiling zerocopy v0.7.35 + Compiling ppv-lite86 v0.2.20 + Compiling rand_chacha v0.3.1 + Compiling rand v0.8.5 + Compiling guessing_game v0.1.0 (file:///projects/guessing_game) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.48s +``` + +Woho wait a minute. I have only added one dependency! Just a random +number generator! Why is it building 15 packages now? Where does all +of this come from? Surely these are not dependencies dragged in by +the `rand` crate, *right?* + +Well, apparently they are. If we want to generate a random number, we +need 15 external packages. I would imagine this makes Rust completely +unusable in a professional setting, because nobody would want to audit +15 separate crates just to include a random number generator. And no +professional programmer would include dependencies in a serious project +without first auditing them, *right?* *RIGHT?!* + +I mean, each dependency introduces an extra liability in your project; +it is code that you can't control and you never know when it is going to +break. And this is why serious programmers, select dependencies carefully +and only use them when absolutely necessary... *right?* + +Ahah, of course they do not. Remember the +[letf-pad incident](https://en.wikipedia.org/wiki/Npm_left-pad_incident)? +Apparently Cargo is of the same breed as npm. And then we wonder why +the software industry is such a shitshow. *Just keep building on top +of the house of cards, man. It's going to be fine, man, don't bother +implementing low-level stuff, that's hard. Just trust random people on +the internet and ship their code, man.* + +Bleah. + +## The Rust language + +So far I talked mostly about the tooling around Rust, but I said nothing +about the language itself. I want to write some code before making a +more informed opinion about it, but the first impression is that it is +really nice! + +I love the type system, especially +[enums](https://doc.rust-lang.org/book/ch06-00-enums.html) +and `match`. I like that this and stuff like `Option<T>` are +first-class citizens; you can do something similar in C++ with +[`std::variant`](https://doc.rust-lang.org/book/ch06-00-enums.html) and +[`std::optional`](https://en.cppreference.com/w/cpp/utility/optional.html), +but the syntax quickly gets messy. I guess this shows the advantage of +making a new language from scratch instead of being forced to live with +decades-old syntax for backwards compatibility. + +I especially like this mechanism - Rust's enums, or `std::variant`, or +tagged unions, whatever you like to call them - for error handling. I find +them a better solution than exception, because they enforce correctness: +if your function can fail, your coding must handle it; you can't just +let an error message bubble up from the depths of Hell, wreak havoc in +your control flow and face the user with an "Object reference not set +to an instance of an object". I think it is great that Rust endorses +this more thorough way of error handling from the start. + +On the less-nice side of things, apparently +[integer overflow](https://doc.rust-lang.org/book/ch03-02-data-types.html?highlight=overflow#integer-overflow) +is still an issue. Eh. At least when compiling in debug mode overflows +are caught, which is nice. But in release mode the program is +going to "panic", which I was not expecting. I mistakenly thought +this was one of the issues that Rust was solving at compile time, +or maybe even at the level of language specification. + +This brings me to another problem that I thought Rust would solve, +but it does not: accessing a out-of-bound index of an array also leads +to a panic. I was expecting "don't allow indexes out of bound" to be +included in Rust's compile-time checks, somehow. For example in Ada you +can declare an array to accept only values of a specific +[range type](https://en.wikibooks.org/wiki/Ada_Programming/Types/range), +so that out-of-bound errors are completely eliminated. Apparently a +"panic" is memory safe behavior, but it is definitely not *correct* +behavior. I guess this was a big misunderstanding from my side: memory +*safety* does not mean memory *correctness* - Rust still allows you to +make mistakes related to memory. + +## Documentation + +A quick note on the documentation: it is very nice. The book is well +written, and I am enjoying reading through it, even though it is very +basic for me. It is also available offline - at least when installing +rust via `rustup` - which is something I always appreciate. + +The compiler error messages, which I consider to be part of the +documentation as well, are simply amazing. Not only they are usually very +precise, not only they often suggest a fix, but they also point you out to +some piece of documentation so that you can learn why your code is wrong. +For example, if you try to compile this code with `rustc filename.rs`: + +``` +fn main() { + println("Hello, world!"); +} +``` + +You get: + +``` +error[E0423]: expected function, found macro `println` + --> hello.rs:2:2 + | +2 | println("Hello, world!"); + | ^^^^^^^ not a function + | +help: use `!` to invoke the macro + | +2 | println!("Hello, world!"); + | + + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0423`. +``` + +And if you run `rustc --explain E0423` as suggested: + +``` +An identifier was used like a function name or a value was expected and the identifier exists but it belongs to a different namespace. + +Erroneous code example: + +struct Foo { a: bool }; + +let f = Foo(); +// error: expected function, tuple struct or tuple variant, found `Foo` +// `Foo` is a struct name, but this expression uses it like a function name + +Please verify you didn't misspell the name of what you actually wanted to use here. Example: + +fn Foo() -> u32 { 0 } + +let f = Foo(); // ok! + +It is common to forget the trailing ! on macro invocations, which would also yield this error: + +println(""); +// error: expected function, tuple struct or tuple variant, +// found macro `println` +// did you mean `println!(...)`? (notice the trailing `!`) + +Another case where this error is emitted is when a value is expected, but something else is found: + +pub mod a { + pub const I: i32 = 1; +} + +fn h1() -> i32 { + a.I + //~^ ERROR expected value, found module `a` + // did you mean `a::I`? +} +``` + +I would not be surprised if the main reason why Rustaceans are so +enthusiastic about the language was actually how nice `rustc` is. +I expect working with this tool will be very pleasant. + +## Moving on! + +Apart from the +[culture shock](https://en.wikipedia.org/wiki/Culture_shock) of the +installation process and the dependency management, my first impression +of Rust is quite positive. But as I said, I am just getting started +and my judgement is very superficial. I do want to write some small +project in Rust, and I think I'll start from re-writing a simple [math +library for modular arithmetic](../2025-01-21-taming-cpp-templates) +that I wrote in C++ some time ago. + +Stay tuned for more 🦀