←back to thread

128 points RGBCube | 2 comments | | HN request time: 0s | source
Show context
hyperbrainer ◴[] No.44497751[source]
> we cannot just require all generic parameters to be Clone, as we cannot assume they are used in such a way that requires them to be cloned.

I don't understand what "used in such a way requires them to be cloned" means. Why would you require that?

replies(3): >>44497760 #>>44497770 #>>44497789 #
tinco ◴[] No.44497789[source]
That's the crux of the article. There's no good reason for this requirement, at least none that arise in the article, so the author concludes it must be a mistake.

I think it's a bit cynical that it would cost at least 4 years for this change to be admitted into the compiler. If the author is right and there is really no good reason for this rule, and I agree with the author in seeing no good reason, then it seems like something that could be changed quite quickly. The change would allow more code to compile, so nothing would break.

The only reason I could come up with for this rule is that for some other reason allowing non complying type parameters somehow makes the code generation really complex and they therefore postponed the feature.

replies(1): >>44497915 #
1. the_mitsuhiko ◴[] No.44497915[source]
> The only reason I could come up with for this rule is that for some other reason allowing non complying type parameters somehow makes the code generation really complex and they therefore postponed the feature.

The history of this decision can be found in details in this blog post: https://smallcultfollowing.com/babysteps//blog/2022/04/12/im...

The key part:

> This idea [of relaxing the bounds] is quite old, but there were a few problems that have blocked us from doing it. First, it requires changing all trait matching to permit cycles (currently, cycles are only permitted for auto traits like Send). This is because checking whether List<T> is Send would not require checking whether Option<Rc<List<T>>> is Send. If you work that through, you’ll find that a cycle arises. I’m not going to talk much about this in this post, but it is not a trivial thing to do: if we are not careful, it would make Rust quite unsound indeed. For now, though, let’s just assume we can do it soundly.

> The other problem is that it introduces a new semver hazard: just as Rust currently commits you to being Send so long as you don’t have any non-Send types, derive would now commit List<T> to being cloneable even when T: Clone does not hold.

> For example, perhaps we decide that storing a Rc<T> for each list wasn’t really necessary. Therefore, we might refactor List<T> to store T directly […] We might expect that, since we are only changing the type of a private field, this change could not cause any clients of the library to stop compiling. With perfect derive, we would be wrong.2 This change means that we now own a T directly, and so List<T>: Clone is only true if T: Clone.

replies(1): >>44498174 #
2. josephg ◴[] No.44498174[source]
Yeah; I think this argument makes sense. With perfect derive, #[derive(Clone)] has a bunch of implicit trait bounds which will change automatically as the struct changes. This has semver implications - and so we might want to be explicit about this rather than implicit.

We could solve this by having developers add trait bounds explicitly into the derive macro.

Currently this:

    #[derive(Clone)]
    struct Foo<T>(Arc<T>)
expands to:

    impl Clone for Foo where T: Clone { ... }
Perfect derive would look at the struct fields to figure out what the trait bounds should be. But it might make more sense to let users set the bound explicitly. Apparently the bon crate does it something like this:

    #[derive(Clone(bounds(Arc<T>: Clone)))]
Then if you add or remove fields from your struct, the trait bounds don't necessarily get modified as a result. (Or, changing those trait bounds is an explicit choice by the library author.)