Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

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.



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.


The main reason I'm not super fond of the way it currently works is that it can be a bit confusing in code reviews. I've joined several teams over the years working on Rust codebases around a year old where most of the team hadn't used Rust beforehand, with the idea that my Rust experience can help the team grow in their Rust knowledge and mature the codebase over time. I can recall numerous times when I've seen a trait like Debug or Clone manually implemented by someone newer to Rust where the implementation is identical to what would be generated by automatically deriving it, with a roughly equal split between times when they did actually need to manually implement it for the reasons described in this article and times when they totally could have derived the trait but didn't realize. If I can't look at a Clone implementation that just manually clones every field exactly the same way as deriving it would and immediately know whether it would be possible to derive it after over 10 years of Rust experience, I can't possibly expect someone with less than a year of Rust experience to do that, so my code review feedback ends up having to be a question about whether they tried to derive the trait or not (and to try it and keep it like that if it does work) rather than being able to let them know for sure that they can just derive the trait instead.

I guess at a higher level, my issue with the way it currently works is that it's a bit ambiguous with respect to the intent of the developer. If it were possible to derive traits in the cases the article describes, seeing a manual implementation would be immediately clear that this was what the developer chose to write. The way it works right now means that I can't tell the difference between "I tried to derive this, but it didn't work, so I had to implement it manually as a fallback" and "I implemented this manually without trying to derive it first". It's a minor issue, but I think small things like this add up in the overall experience of how hard it is for someone to learn a language, and I'd argue that it's exactly the type of thing that Rust has benefited from caring about in the past. Rust has a notoriously sharp learning curve, and yet it's grown in popularity quite a lot over the past decade, and I don't think that would have been possible without the efforts of those paying attention to the smaller rough edges in the day-to-day experience of using the language.


> 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.


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.


“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.


The solution is to use a derive macro like derivative or educe.


skill issue. Arc should allow Clone, even if the underlying is not `impl Clone`.


Arc _does_ allow clone without the underlying implementation not being clone. That's exactly why it's so unexpected that having an `Arc<T>` field in a struct doesn't allow deriving clone unless `T` is also clone; it doesn't actually matter to the implementation, and you end up having to write the exact same thing manually that would be generated by deriving it if the compiler actually allowed it.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: