4 ms·
> much of the complexity I’d unconsciously attributed to the domain — “this is what systems programming is like” — was in fact a consequence of deliberate Rust
by skohan 6y ago
> much of the complexity I’d unconsciously attributed to the domain — “this is what systems programming is like” — was in fact a consequence of deliberate Rust design decisions.
This is something I've thought a lot about as I've started to reach the "hundreds of hours" of working with Rust mark. Besides attributing the complexity of Rust to the domain of systems programming, I think a lot of Rust's complexity often gets attributed as a trade-of you're making for the safety guarantees afforded by the borrow checker. But I think a lot of the complexity in Rust is not related to the borrow checker at all, and is just a result of certain design decisions in the language.
For instance, taking the module system as an example, in general, I can declare a dependency in mu `Cargo.toml` file like this: `"crate_name"`, and then import it into a given source file using the use declaration: `use crate_name`. However there's a special case, where if the crate name uses dashes, like `"crate-name"`, then the compiler will implicitly resolve to that from a use declaration using underscores: `use crate_name`.
Similarly, if wade into an unfamiliar code-base, and I see a use declaration like this: `use path::to::foo", if I want to look for the code for this, it could be in one of three places:
1. The file `src/path/to/foo.rs`
2. The file `src/path/to/foo/mod.rs`
3. Some other file, based on re-export via a `pub use` declaration.
So in order to use Rust effectively, I have to just sort of know about all these implicit behaviors of the compiler, and in my experience it took months to learn enough of these tricks and corners to really just be able to sit down with an idea and start coding in Rust without consulting documentation and examples regularly. And even after that the compiler still surprises me sometimes.
To give another example of somewhat vexing implicit behavior which does relate to the ownership system, just today I had a block of code which looked like this:
let x = some_value;
match x { ... }
x = some_other_value;
match x { ... }
Which compiled fine. And then by commenting out the second assignment of `x`:
let x = some_value;
match x { ... }
// x = some_other_value;
match x { ... } // <-- use after move
suddenly I had an ownership error, because `x` was moved by the first match statement. So here what was really going on is that the compiler was "helping me" using implicit rules to establish that `x` was referring to a different memory location after the assignment. It's an example of how Rust has all these implicit behaviors and overlaid systems which are intended to make working in a borrow-checked context easier, but in practice what this often means is that when you change something in such a way that one of these implicit systems breaks down, it can cause a failure in what seems like a totally unrelated place, which can be very surprising.
I wonder if part of this has to do with the fact that Rust seems to appeal to a certain type of programmer who is attracted to esoteric topics and arcane knowledge, so the fact that Rust is essentially an unlimited well of complexity is more a feature than a bug to them. But I have been thinking a lot about what a programming language would look like which has an ownership system like Rust, algebraic types, and a trait system, but puts a ruthless emphasis on productivity and eschewing complexity.
- paavohtl 6y ago> suddenly I had an ownership error, because `x` was moved by the first match statement How do you propose it should work instead? Disallow using match with owned values, so that match never moves? Disallow assigning to a variable where the value has been moved, requiring a new or shadowed variable instead? You could do either of those things, but neither would remove complexity, just move it elsewhere and perhaps cause some new issues. It's easy to complain about complexity, but almost all of it exists for a reason, and hasn't been added just because Rust programmers are "attracted to esoteric topics and arcane knowledge". The implicit behavior you are talking about in this case is the ownership system.
- skohan 6y agoTo me it would be conceptually simpler to consider one named variable as analogous to a memory location. So I would not be able to write to assign to a variable after a move, because essentially I could read that as "assign some_other_value to the memory location x", which in this case is already owned by the match statement. It seems here that after the assignment, `x` is essentially being implicitly re-declared as a shadowed variable. I have to think in terms of "can the compiler find a way to make this safe" rather than having a more-or-less one-to-one mapping between the code I write and the machine behavior.
- paavohtl 6y ago> To me it would be conceptually simpler to consider one named variable as analogous to a memory location. But that's how it already is! The variable x is conceptually a single memory location within the stack frame. The match statement doesn't own the variable x, it owns its previous contents. It has effectively removed whatever was in x and whatever remains in x's memory location is no longer accessible, but the variable is still there.
- deleted 6y ago[deleted]
- sdht0 6y ago