←back to thread

128 points RGBCube | 2 comments | | HN request time: 0.596s | source
Show context
csomar ◴[] No.44497997[source]
Derive Clone is not broken. It is basic. I’d say this is a good area for a dependency but not the core Rust functionality. Keep derive simple and stupid, so people can learn they can derive stuff themselves. It also avoids any surprises.
replies(3): >>44498105 #>>44498162 #>>44498173 #
josephg ◴[] No.44498105[source]
I disagree. I think the current behaviour is surprising. The first time I ran into this problem, I spent half an hour trying to figure out what was wrong with my code - before eventually realising its a known problem in the language. What a waste of time.

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.

replies(3): >>44498184 #>>44498204 #>>44498538 #
MangoToupe ◴[] No.44498184[source]
I don't see it as a problem, personally. It's consistent behavior that I don't find surprising at all, perhaps because I internalized it so long ago. I can understand your frustration tho

> in the most obvious way.

What people find obvious is often hard to predict.

replies(2): >>44499395 #>>44500199 #
josephg ◴[] No.44500199[source]
> 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.

replies(1): >>44500310 #
1. MangoToupe ◴[] No.44500310[source]
IDK, I guess we'll just have to disagree on this. The original rationale for restricting derive makes more sense to me than not restricting it. What you see as obvious strikes me as potentially dangerous.

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.

replies(1): >>44515958 #
2. josephg ◴[] No.44515958[source]
“Obvious” to me is an appeal to ocham’s razor. Whether you’re conscious of it or not, we all deeply feel that our systems should obey the simplest theory.

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.