Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically derive {Clone,PartialEq,PartialOrd} when {Copy,Eq,Ord} are derived #1028

Closed
wants to merge 1 commit into from

Conversation

Projects
None yet
@bstrie
Copy link
Contributor

bstrie commented Apr 1, 2015

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Apr 1, 2015

I'm a fan. This feels like an annoying papercut we should fix.

@erickt

This comment has been minimized.

Copy link

erickt commented Apr 1, 2015

Thanks for writing this up! One thing to note is that as of right now generic types like Option<T> cannot use the short form

impl<T: Clone> Clone for Option<T> {
    fn clone(&self) -> Self { *self }
}

Because it may wrap non-Copy types. We would need to have specialization to get that fast path working.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Apr 1, 2015

I imagine that if we introduce specialization, we will add a single impl:

#[low_priority] // or whatever
impl<T: Copy> Clone for T {
    fn clone(&self) -> Self { *self }
}

and then just modify #[derive(Copy)] to no longer generate a Clone impl.

(Same for the other traits.)


# Unresolved questions

Is it reasonable to assume that no user who automatically derives `Eq`/`Ord` has manually implemented `PartialEq`/`PartialOrd`? If this assumption fails to hold, what behavior should result?

This comment has been minimized.

@alexcrichton

alexcrichton Apr 1, 2015

Member

Cargo has a few examples of deriving Eq with a manual implementation of PartialEq. I'd be fine just moving some words from here to the "Drawbacks" section, though!

@glaebhoerl

This comment has been minimized.

Copy link
Contributor

glaebhoerl commented Apr 2, 2015

Why not "automatically derive supertraits of derived traits" in general? So #[derive(Ord)] (for example) also derives PartialEq, Eq, and PartialOrd, not just the latter.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Apr 2, 2015

@glaebhoerl hmm, good point. #[derive(Ord)] probably ought to imply #[derive(Eq)] too.


An implementation of this RFC can be seen here: https://github.com/rust-lang/rust/pull/23905

# Drawbacks

This comment has been minimized.

@alexcrichton

alexcrichton Apr 2, 2015

Member

One other drawback this may wish to mention is that [T; N] is a type that can implement Copy for all N but not Clone. For example in landing rust-lang/rust#23860 it was discovered that many FFI types could derive Copy but not Clone because they had a field that was along the lines of [u8; 100].

It's quite easy, however, to add a Clone implementation for a type that is Copy, so the drawback would just be that today's #[derive(Copy)] + manual Clone would have to switch to a manual Copy as well (slightly more verbose).

This comment has been minimized.

@nikomatsakis

nikomatsakis Apr 3, 2015

Contributor

Note that @erickt's PR includes a specialized variant of Clone for the case where Copy is also mentioned and the types are not generic, which would have addressed this problem for FFI types at least. (Also this comment applies.)

This comment has been minimized.

@alexcrichton

alexcrichton Apr 3, 2015

Member

Aha, never mind then!

@nikomatsakis nikomatsakis self-assigned this Apr 6, 2015

@nikomatsakis nikomatsakis changed the title Automatically derive certain traits when more fundamental traits are derived Automatically derive {Clone,PartialEq,PartialOrd} when {Copy,Eq,Ord} are derived Apr 6, 2015

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Apr 6, 2015

I spiced up the title to make it clearer what is being requested. @bstrie, hope you don't mind.

@arthurprs

This comment has been minimized.

Copy link

arthurprs commented Apr 8, 2015

@glaebhoerl seems like a great idea

@mdinger

This comment has been minimized.

Copy link
Contributor

mdinger commented Apr 8, 2015

This should fix #441 if subtraits are all automatically derived. I think that's where @glaebhoerl originally suggested it.

@Kintaro

This comment has been minimized.

Copy link

Kintaro commented Apr 8, 2015

I'm all for it. We still have time for breaking changes now before the final, so if we'd do it, now's the time.

@arthurprs

This comment has been minimized.

Copy link

arthurprs commented Apr 8, 2015

@Kintaro This isn't a breaking change.

@seanmonstar

This comment has been minimized.

@glaebhoerl

This comment has been minimized.

Copy link
Contributor

glaebhoerl commented Apr 8, 2015

@arthurprs It is, see this post by @nikomatsakis on Discourse. (Edit: Jynx.)

I think the customary approach in cases like this has been to gather some statistics on how much code would actually break? I suspect that it's not very much at all, and that this is worth doing.

@diwic

This comment has been minimized.

Copy link

diwic commented Apr 8, 2015

Is it not possible to be intelligent enough to do: "Okay, we found a derive(Copy). Let's go look for a manual impl of Clone, and if we find one, use it, otherwise we autogenerate one".

With that logic, would that make this RFC a non-breaking change?

@sfackler

This comment has been minimized.

Copy link
Member

sfackler commented Apr 8, 2015

We can't avoid generating the impl entirely, but we could tag it with an attribute that would tell later stages to strip it if it would conflict with another impl.

@seanmonstar

This comment has been minimized.

Copy link
Contributor

seanmonstar commented Apr 8, 2015

@diwic It might be possible, and if it is, it should definitely be done. However, it may be that expansion of item decorators happens before the session has collected all trait implementations, in which case it couldn't be done.

@nagisa

This comment has been minimized.

Copy link
Contributor

nagisa commented Apr 8, 2015

I prefer it being explicit. Sure, in this case implicit might be ergonomically better here, but it doesn’t match the style of the language, which is explicit.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Apr 8, 2015

@sfackler yes, that's specialization. If we were to add specialization in the future, we could add this backwards compatibilty.

@seanmonstar

This comment has been minimized.

Copy link
Contributor

seanmonstar commented Apr 8, 2015

@nikomatsakis I think he means adding a #[implicitly_derived] attribute, and then if there's a conflict in later stages, removing the conflicting implementation that has said attribute.

@sfackler

This comment has been minimized.

Copy link
Member

sfackler commented Apr 8, 2015

It wouldn't necessarily be specialization in this case, though, since the manual impl and the derived impl could be defined on the exact same bounds:

#[derive(Copy)]
struct Thing(i32);

impl Clone for Thing {
    fn clone(&self) -> Thing {
        *self
    }
}

So we'd need a separate concept of a "low priority" or "weak" implementation or something that'll be discarded if there's any conflict whatsoever.

@pythonesque

This comment has been minimized.

Copy link
Contributor

pythonesque commented Apr 8, 2015

I'm torn on this one. Overall, I do think I'm in favor, especially if we can add specialization in the future.

@glaebhoerl

This comment has been minimized.

Copy link
Contributor

glaebhoerl commented Apr 8, 2015

I think "check if there's already a manual impl, and only derive one if there isn't" versus "always derive an impl, but throw it away if it turns out there's a manual one" is just an implementation detail - the two are observationally equivalent, and there's no need to expose the underlying mechanism to end-users directly. (If I'm not missing something.)

@nstoddard

This comment has been minimized.

Copy link

nstoddard commented Apr 8, 2015

I totally support this. I've always found it annoying how I have to derive both Eq and PartialEq. I don't mind the breakage, especially since very little code will probably be broken (does anyone have statistics on how much would actually break?).

@bluss

This comment has been minimized.

Copy link

bluss commented Apr 8, 2015

We don't need a breaking change, can't we expand deriving in a non-breaking way? That way we have time to do it during a normal release cycle too and not do it in a rush.

For example, what about #[derive(Ord="full")] to derive the whole shebang, or some similar backwards compatible expansion of the current feature.

I think we need to calmly look at nice generically useful solutions just like we used to. Always deriving multiple traits is less flexible, and useful in less situations, even though it is easier when it is applicable. Even just giving the trait group a new derive name is a simpler and more broadly useful solution: #[derive(TotalOrdering)].

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Apr 8, 2015

@seanmonstar @sfackler

The fact that it's a more limited, special-case of specialization doesn't really make me feel better about it, to be honest. I'm definitely opposed to adding some last-minute magic into the trait system without a very strong justification. I want people to be able to understand deriving as simple shorthands for things they could have typed themselves. I don't think the breakage will be that bad if we just do this. In any case, we're trying to gather some numbers based on @brson's tool and PR rust-lang/rust#23905.

@comex

This comment has been minimized.

Copy link

comex commented Apr 8, 2015

In the space of non-breaking solutions, along with the proposed aliases that would cover the supertrait use cases here, I propose one that covers at least Clone, {Partial,}Eq, {Partial,}Ord, Debug, and Hash - i.e. the set of derivable traits that would be useful to implement (especially for the sake of users of a library type, or at worst harmless) for most simple structs with a few integer or string fields. What I'd call 'data-like' (as opposed to 'object-like') structs. I'd personally find this more useful than being able to omit supertraits but still having to write out several different traits for everything.

(edited for clarity since I'm tired)

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Apr 8, 2015

@bluss @comex yes, it's true that shorthand names are a reasonable compromise -- and maybe a better solution. The last time we went through the exercise, we failed to come to much consensus about what those shorthands ought to be, but perhaps we're in a better position now. Things are more stable, and there are a lot more crates we could grep through to try to figure out the most common combinations.

@vadimcn

This comment has been minimized.

Copy link
Contributor

vadimcn commented Apr 8, 2015

Another option would be to add #[derive_explicit(...)] for cases when you don't want auto-derived base traits.
As for the breaking change, can libsyntax detect common cases like #[derive(Copy, Clone)] in existing code and issue a deprecation warning instead of an error?

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Apr 8, 2015

@vadimcn #[derive(Copy, Clone)] doesn't break. It's only if the Clone impl is manually implemented, but Copy is derived.

@bvssvni

This comment has been minimized.

Copy link

bvssvni commented Apr 8, 2015

If specialization is planned at some point, I don't mind waiting for it, since that would be a backward compatible change.

@jpernst

This comment has been minimized.

Copy link

jpernst commented Apr 8, 2015

Speaking as a user who is generally in favor of inference, I have to say I'm against this. At least so long as manually implementing any trait that is also implicitly derived results in an error. Manually implementing one trait should not bar you from deriving another. If this problem can be solved more generally in the future, then this would be a great addition at that time.

@tedsta

This comment has been minimized.

Copy link

tedsta commented Apr 8, 2015

I am for this as long as the specialization gets implemented eventually. If there will never be a way to manually implement PartialEq, for example, and derive Eq, I am against this.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Apr 8, 2015

I think having thought this through I'm now feeling that it's not worth making a breaking change for this. Depending how things work out, we can either have some kind of shorthand notation or perhaps we'll be able to rely on specialization (we'd have to see).

@SiegeLord

This comment has been minimized.

Copy link

SiegeLord commented Apr 9, 2015

I'd rather have a shorthand than this RFC. My code would be broken by it, and I don't see the benefit given that there is a lint (at least for Copy + Clone).

@norcalli

This comment has been minimized.

Copy link

norcalli commented Apr 9, 2015

Logically, if I see something that says #[derive(Copy)] or #[derive(Eq)], I would assume it would also imply a partial comparison or clone, so I don't see that as a big problem. And that way, seeing the word Partial would indicate to me that the type doesn't have a full version faster and clearer than if you had to always specify both. And I can see how if specialization comes in, it would be able to be dropped in, so that's nice. So I'd be tentatively in support of this.

@iopq

This comment has been minimized.

Copy link
Contributor

iopq commented Apr 9, 2015

What's the point of a breaking change to save a few characters? Why not implement it in a non-breaking way (if you have a Clone implementation, let it be used in the Copy implementation) instead of breaking it?

@Gankro

This comment has been minimized.

Copy link
Contributor

Gankro commented Apr 9, 2015

I'm in favour of this. It's weird that we allow the user to even ask for non-sensical derivations (e.g. "only Ord"). I'm sympathetic to manual implementations becoming a bit more verbose, but those are the exception, and we should optimize for the common case. This change makes Rust "just work" better. Also it allows us to make stronger assumptions about derived implementations: if they derive(Ord) we know exactly how Ord is implemented. If we decide to introduce UnsafeOrd or whatever then we can silently upgrade all derive(Ord)'s to derive(UnsafeOrd)'s (iff they contain only UnsafeOrd's).

The fact that it saves key-strokes is simply gravy, but not the main point IMHO.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Apr 9, 2015

I think I prefer the shorthand idea. @aturon suggested something like #[derive(Ord*)], meaning "Ord and all of its supertraits". This is not legal syntax today, but I've been wanting to expand the "grammar" of attributes to permit arbitrary token trees for a while now...anyway, something like that seems like a simple and consistent rule we could easily add later, and it would keep the workings of derive pretty straightforward.

@trws

This comment has been minimized.

Copy link

trws commented Apr 9, 2015

One option I haven't seen mentioned, forgive me if I missed it, would be to allow a user to specify which supertraits they wish to implement manually as part of the syntax. Something along the lines of #[derive(Copy, -Clone)] would make it possible to explicitly supply a manual implementation of the supertrait by masking off the derived version. It would remain breaking, but avoids forcing a manual implementation of the subtrait where a supertrait needs specialization.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Apr 10, 2015

After much deliberation, we've decided not to pursue this avenue, because it doesn't seem worth making a breaking change -- even a small one. Moreover, this change is not universally popular (e.g., 1, 2, 3) and may be pedagogically challenging, as it breaks the correspondence where deriving a trait is exactly equivalent to implement it (since implementing a trait does not automatically implement its supertraits). The alternative of creating shorthands seems to be a promising alternative, and might also go further in reducing verbosity by permitting groups of common traits to be group together. Therefore, I'm going to close this RFC as postponed under issue #441. @bstrie, thanks for writing this up and @erickt, thanks for the preliminary implementation. Thanks everybody else for the comments and thoughts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.