The language & compiler should be unsurprising. If you have language feature A, and language feature B, if you combine them you should get A+B in the most obvious way. There shouldn't be weird extra constraints & gotchas that you trip over.
> in the most obvious way.
What people find obvious is often hard to predict.
It’s not so terribly difficult to figure out what the expected behaviour is here. If I can write impl Clone in exactly the same way #[derive(Clone)] would do it, I should be able to just go ahead and use derive to do it. That seems pretty obvious to me.
Then again, I never had much respect for "obviousness" (as a concept, not in terms of code that is readable); the concept doesn't strike me as very useful except for papering over disagreement.
If addition in rust worked normally except when you add the number 4, the program panicked, that would be ridiculous. But why? Because it is inconvenient for one. And it’s not obvious. You need a more complex theory to model language like that.
The question with derive(Clone) is “what is the most straight forward theory” and if the actual language is more complex, we have to ask ourselves if the extra complexity is worth the cost.
If you spend 2 minutes thinking about it, as the blog post author said, if you want to implement clone for Foo<T>, it should be totally irrelevant if T implements clone or not. What matters is if the members of the struct implement clone. And that’s a completely independent question. Maybe T is clone and the struct members are not (eg the struct stores Cell<T>). Maybe the struct members are clone and T is not (eg if the struct stores Arc<T>). It’s a very strange, idiosyncratic theory that derive should have an invisible trait bound on T: Clone. It was also pretty surprising to the blog post author.
Sometimes complex theories pay their own rent. Like the borrow checker. Or fixed size integers. But if the theory is hidden, hard to learn, and doesn’t seem to help programmers, I’d call it a bug. Or a kludge. It’s certainly not an elegant programming language feature. This is why bringing up obviousness is relevant. Because good language features are either simple (and thus obvious) or they help me program enough to be worth the complexity they bring. This is neither.
This seems like a kludge to me. Derive should either impl clone when the children impl clone, or be universal, or univeral with optional custom trait bounds specified by the caller. (Eg derive(Clone where Foo<T>: Clone).
The advantage of a universal implementation is that you’d get more obvious compilation errors. “impl Clone of Mystruct<T>(T) requires T: Clone. Fix by adding where T: Clone to derive or manually implementing clone”. Simple. Straightforward. Obvious.