Why aren't some basic traits automatically derived?

While we are at it: C++ manages to call copy constructors automatically upon pass-by-value, so why can't Rust do it too? Why do we have to bother with the oh-so-inconvenient move semantics and explicit clone()s? Why aren't the developers of Rust smart enough to recognize that this would be so much more convenient?


As it turns out, the answer is the same, every single time. If something nontrivial happens, be it at runtime (such as allocating memory) or at compile time (such as awarding a type some sort of capability by means of impling a trait), that nontrivial action should be visible to the programmer. If it's dangerous, it should also be noisy and scary, but if it's safe only nontrivial, it should still be visible.

A good programming language is not one that lets you write the least possible amount of code. A good programming language is one that shows the intent to the human reader. One that actively guides his/her mental model towards the actual, real, honest-to-God thing that the compiler sees and the code it generates.

People are spectacularly bad at non-local reasoning. The only thing they are worse at is keeping huge amounts of small, invisible details in their head. The tools that we could use as crutches, such as full-fledged on-the-fly type checkers, are immature at best and come with their own learning curve (ever tried setting up RLS for an IDE?), therefore most of us will fall back to simply (rip)grepping for raw text. But grepping for an empty string is not particularly productive, to put it mildly.

Consequently, it is usually a good thing when the programmer needs to spell out his/her intent, because it will be easier on future readers' eyes and ripgreps. Of course, there are legitimate exceptions to this rule. When programmers tend to forget certain things, it's better to automate them away. (I meant the things being forgotten, not the programmers.) For example, people regularly forget to free dynamically-allocated memory and to close file handles. Or they just do it at the wrong time. Hence, Rust has implicit destruction aka RAII, which is a completely different perspective of language design, but the goal is the same: make the code correct and safe.

Finally, as a concrete example, there are other "basic" (read: commonly-used std) traits that could be automatically derived but shouldn't. For example, Debug can also be inferred trivially from the structure of a type. However, what if I am writing a cryptography library and I don't want to expose secret keys, passwords, and other credentials? Should I remember to opt out of Debug every single time I add a new type? That would be insane. One day I would forget to opt out of just one teensy type, and someone would go ahead and debug-log passwords of millions of people on their server in plain text. Of course, this has totally never ever happened before, so I'm pulling a slippery slope argument here, and I should be ashamed of myself for how low I think of programmers.


TL;DR: if we have learnt anything from the past couple of decades of programming language design, it is that there is no such thing as a good surprise in programming. So, let's design languages that eliminate the surprise factor instead of fueling it even further.

12 Likes