Skip to content

document assumptions about Clone and Eq traits #144330

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

Merged
merged 5 commits into from
Aug 11, 2025

Conversation

gewitternacht
Copy link
Contributor

Most standard library collections break if Clone has a non-standard implementation which violates x.clone() == x. Here the resulting broken behaviour of different collections is shown. I originally created an issue at rust-lang/hashbrown#629, but the conclusion there was that x.clone() resulting in an object that compares equal to the original one is probably a very universal assumption. However, this assumption is (to my knowledge) not documented anywhere.

I propose to make this assumption explicit in the Clone trait documentation. The property that seems the most reasonable to me is the following: When implementing both Clone and PartialEq, then

x == x -> x.clone() == x

is expected to hold. This way, when also implementing Eq, it automatically follows that x.clone() == x has to hold, which should be enough for the collections to not break. At the same time, the property also works for the "normal" elements of a type with PartialEq. For the wording, I tried to follow the Hash and Eq documentation.

As I am fairly new to Rust, it might well be that this property cannot be generally expected – it seems reasonable to me, but any counter-examples or critique, both content- and wording-wise, would be very welcome. If the property turns out to be too general, I would suggest to at least document the assumption of x.clone() == x for the collections somehow.

An additional thought of mine:
If it is indeed generally expected that x == x -> x.clone() == x, then, for the sake of completeness, one could also define that x != x -> y != y for y = x.clone() should hold, i.e., that an object that did not compare equal to itself before cloning, should also not compare equal to itself afterwards.

@rustbot
Copy link
Collaborator

rustbot commented Jul 22, 2025

r? @Mark-Simulacrum

rustbot has assigned @Mark-Simulacrum.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Jul 22, 2025
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

/// Violating this property is a logic error. The behavior resulting from a logic error is not
/// specified, but users of the trait must ensure that such logic errors do *not* result in
/// undefined behavior. This means that `unsafe` code **must not** rely on the correctness of these
/// methods.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit confusing to say "these methods" here IMO - maybe "this property"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, I could change it to "... rely on this property being satisfied."

The reason behind the "correctness of these methods" wording is that it is phrased like this in the documentation on Hash and Eq, as well as the Eq and PartialEq trait documentation. I found it confusing there, too, but I thought it was just because I lacked experience with the terminology.
So, would you say the wording in these other places is confusing, too? And if so, should I make it part of this PR to change it there, too? (my first time doing a PR like this, so I'm still learning best practices)

@Mark-Simulacrum Mark-Simulacrum added T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). needs-fcp This change is insta-stable, or significant enough to need a team FCP to proceed. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jul 27, 2025
@Mark-Simulacrum
Copy link
Member

Moving into libs-api queue for FCP, since this is a new property (even if likely already required implicitly) so merits some team discussion.

@Amanieu Amanieu removed the T-libs Relevant to the library team, which will review and decide on the PR/issue. label Jul 30, 2025
@@ -139,6 +139,30 @@ mod uninit;
/// // Note: With the manual implementations the above line will compile.
/// ```
///
/// ## `Clone` and `PartialEq`/`Eq`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unclear whether this should apply to PartialEq.

However this definitely needs to be extended to include Hash and Ord since HashMap and BTreeMap rely on this. It could also have vague wording that this may extend to other traits as well.

Copy link
Contributor Author

@gewitternacht gewitternacht Aug 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unclear whether this should apply to PartialEq.

Do you mean this in the sense that the property cannot be required of PartialEq and Clone as we might have to deliberalty break it in some contexts? Or in the sense that we just don't know of anything that depends on this property for PartialEq?
In case of the latter, I would argue that the documentation of Clone – "creation of a duplicate value" – very strongly suggests that I get the "same" thing by cloning, and PartialEq defines "sameness" of objects. E.g., if f == 1.0_f64, I expect that f.clone() == 1.0_f64, too. But without the property x == x -> x.clone() == x, this is never explicitly guaranteed anywhere.

However this definitely needs to be extended to include Hash and Ord since HashMap and BTreeMap rely on this. It could also have vague wording that this may extend to other traits as well.

I only wrote about PartialEq/Eq because x.clone() == x already ensures that clone preserves the hash and ordering of elements, when put together with the guarantees that are required of Hash (see "Hash and Eq") and Ord (due to the conditions for PartialOrd). But I could make this interaction explicit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I think about it, you are correct and PartialEq is the right place to introduce this requirement.

@Amanieu
Copy link
Member

Amanieu commented Aug 10, 2025

@bors r+ rollup

@bors
Copy link
Collaborator

bors commented Aug 10, 2025

📌 Commit fadc961 has been approved by Amanieu

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). labels Aug 10, 2025
Zalathar added a commit to Zalathar/rust that referenced this pull request Aug 11, 2025
…=Amanieu

document assumptions about `Clone` and `Eq` traits

Most standard library collections break if `Clone` has a non-standard implementation which violates `x.clone() == x`. [Here](https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=b7fc6dfa8410cbb673eb8d38393d81de) the resulting broken behaviour of different collections is shown. I originally created an issue at rust-lang/hashbrown#629, but the conclusion there was that `x.clone()` resulting in an object that compares equal to the original one is probably a very universal assumption. However, this assumption is (to my knowledge) not documented anywhere.

I propose to make this assumption explicit in the `Clone` trait documentation. The property that seems the most reasonable to me is the following: When implementing both `Clone` and `PartialEq`, then
```text
x == x -> x.clone() == x
```
is expected to hold. This way, when also implementing `Eq`, it automatically follows that `x.clone() == x` has to hold, which should be enough for the collections to not break. At the same time, the property also works for the "normal" elements of a type with `PartialEq`. For the wording, I tried to follow the [`Hash` and `Eq`](https://doc.rust-lang.org/std/hash/trait.Hash.html#hash-and-eq) documentation.

As I am fairly new to Rust, it might well be that this property cannot be generally expected – it seems reasonable to me, but any counter-examples or critique, both content- and wording-wise, would be very welcome. If the property turns out to be too general, I would suggest to at least document the assumption of `x.clone() == x` for the collections somehow.

An additional thought of mine:
If it is indeed generally expected that `x == x -> x.clone() == x`, then, for the sake of completeness, one could also define that `x != x -> y != y for y = x.clone()` should hold, i.e., that an object that did not compare equal to itself before cloning, should also not compare equal to itself afterwards.
bors added a commit that referenced this pull request Aug 11, 2025
Rollup of 7 pull requests

Successful merges:

 - #143949 (Constify remaining traits/impls for `const_ops`)
 - #144330 (document assumptions about `Clone` and `Eq` traits)
 - #144350 (std: sys: io: io_slice: Add UEFI types)
 - #144558 (Point at the `Fn()` or `FnMut()` bound that coerced a closure, which caused a move error)
 - #145149 (Make config method invoke inside parse use dwn_ctx)
 - #145227 (Tweak spans providing type context on errors when involving macros)
 - #145228 (Remove unnecessary parentheses in `assert!`s)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit 2c314f9 into rust-lang:master Aug 11, 2025
10 checks passed
@rustbot rustbot added this to the 1.91.0 milestone Aug 11, 2025
rust-timer added a commit that referenced this pull request Aug 11, 2025
Rollup merge of #144330 - gewitternacht:document-clone-eq, r=Amanieu

document assumptions about `Clone` and `Eq` traits

Most standard library collections break if `Clone` has a non-standard implementation which violates `x.clone() == x`. [Here](https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=b7fc6dfa8410cbb673eb8d38393d81de) the resulting broken behaviour of different collections is shown. I originally created an issue at rust-lang/hashbrown#629, but the conclusion there was that `x.clone()` resulting in an object that compares equal to the original one is probably a very universal assumption. However, this assumption is (to my knowledge) not documented anywhere.

I propose to make this assumption explicit in the `Clone` trait documentation. The property that seems the most reasonable to me is the following: When implementing both `Clone` and `PartialEq`, then
```text
x == x -> x.clone() == x
```
is expected to hold. This way, when also implementing `Eq`, it automatically follows that `x.clone() == x` has to hold, which should be enough for the collections to not break. At the same time, the property also works for the "normal" elements of a type with `PartialEq`. For the wording, I tried to follow the [`Hash` and `Eq`](https://doc.rust-lang.org/std/hash/trait.Hash.html#hash-and-eq) documentation.

As I am fairly new to Rust, it might well be that this property cannot be generally expected – it seems reasonable to me, but any counter-examples or critique, both content- and wording-wise, would be very welcome. If the property turns out to be too general, I would suggest to at least document the assumption of `x.clone() == x` for the collections somehow.

An additional thought of mine:
If it is indeed generally expected that `x == x -> x.clone() == x`, then, for the sake of completeness, one could also define that `x != x -> y != y for y = x.clone()` should hold, i.e., that an object that did not compare equal to itself before cloning, should also not compare equal to itself afterwards.
github-actions bot pushed a commit to model-checking/verify-rust-std that referenced this pull request Aug 13, 2025
…=Amanieu

document assumptions about `Clone` and `Eq` traits

Most standard library collections break if `Clone` has a non-standard implementation which violates `x.clone() == x`. [Here](https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=b7fc6dfa8410cbb673eb8d38393d81de) the resulting broken behaviour of different collections is shown. I originally created an issue at rust-lang/hashbrown#629, but the conclusion there was that `x.clone()` resulting in an object that compares equal to the original one is probably a very universal assumption. However, this assumption is (to my knowledge) not documented anywhere.

I propose to make this assumption explicit in the `Clone` trait documentation. The property that seems the most reasonable to me is the following: When implementing both `Clone` and `PartialEq`, then
```text
x == x -> x.clone() == x
```
is expected to hold. This way, when also implementing `Eq`, it automatically follows that `x.clone() == x` has to hold, which should be enough for the collections to not break. At the same time, the property also works for the "normal" elements of a type with `PartialEq`. For the wording, I tried to follow the [`Hash` and `Eq`](https://doc.rust-lang.org/std/hash/trait.Hash.html#hash-and-eq) documentation.

As I am fairly new to Rust, it might well be that this property cannot be generally expected – it seems reasonable to me, but any counter-examples or critique, both content- and wording-wise, would be very welcome. If the property turns out to be too general, I would suggest to at least document the assumption of `x.clone() == x` for the collections somehow.

An additional thought of mine:
If it is indeed generally expected that `x == x -> x.clone() == x`, then, for the sake of completeness, one could also define that `x != x -> y != y for y = x.clone()` should hold, i.e., that an object that did not compare equal to itself before cloning, should also not compare equal to itself afterwards.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-fcp This change is insta-stable, or significant enough to need a team FCP to proceed. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants