-
-
Notifications
You must be signed in to change notification settings - Fork 450
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
Derive PartialEq+Eq. #975
Derive PartialEq+Eq. #975
Conversation
@@ -61,11 +61,21 @@ impl<T> fmt::Debug for Array64<T> { | |||
write!(f, "Array64 {{}}") | |||
} | |||
} | |||
impl<T> ::core::cmp::PartialEq for Array64<T> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some places used full paths for the ops traits, so I wasn't sure if that's preferred here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't care a lot. Since Edition 2018 you don't need the leading ::
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you have no preference, I'll just leave as is. If you'd prefer I remove the leading ::
I can do that too.
@@ -28,6 +28,19 @@ pub struct ChaCha { | |||
pub(crate) d: vec128_storage, | |||
} | |||
|
|||
// Custom PartialEq implementation as `vec128_storage` doesn't implement it. | |||
impl ::core::cmp::PartialEq for ChaCha { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems pretty non-ideal, but I'm hoping to not have to also try to get this change into vec128_storage
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm vec128_storage
doesn't provide this Into implementation on all platforms. I'm going to do a transmute
to [u32; 4]
to see if that gets the build passing, but I'd like some input here on what an acceptable approach is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing wrong with a custom implementation, but that unsafe
code — we've been trying to minimise usage, so I'm not keen on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I go try to get PartialEq+Eq
derived for vec128_storage
upstream? I don't think there's a cross target way currently available to compare these without unsafe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could ask @kazcw about that, but looks to me like you can just do let b: [u32; 4] = self.b.into();
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My previous version used Into<[u32; 4]>
but it's not implemented on all platforms. Though that doesn't make a ton of sense to me based on the code you linked. Relevant build log: https://travis-ci.org/github/rust-random/rand/jobs/682708945
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see this code was added since ppv-lite
0.2.6. @kazcw could you release a new version please?
I don't wish to add this unsafe code, which makes us blocked on ppv-lite
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dhardy Sure, I can do that this weekend.
rand_core/CHANGELOG.md
Outdated
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. | |||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) | |||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | |||
|
|||
## [Unreleased] | |||
- Derive PartialEq+Eq for BlockRng and BlockRng64 | |||
- Add PartialEq+Eq constraint to BlockRngCore::Results |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this reasonable? Also should BlockRngCore
itself have a where Self: PartialEq + Eq
constraint at this point?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR! Unfortunately, there are a few issues..
CHANGELOG.md
Outdated
@@ -8,6 +8,9 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). | |||
|
|||
You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. | |||
|
|||
## [Unreleased] | |||
- Derive PartialEq+Eq for StdRng, SmallRng, ThreadRng, ReseedingRng, ReseedingCore, and StepRng (#975) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't make any sense for ThreadRng
because that's just a handle. Handles cannot cross thread boundaries so any two handles which can be compared should be equal. I'm also not sure it's useful on the reseeding constructs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fair. I was thinking that comparing the internal pointers would be reasonable (assuming that's what the derive does for NonNull
). You bring up a good point about it not being something you'd store and pass across threads. I'll remove the derive on that one.
Cargo.toml
Outdated
@@ -73,7 +73,7 @@ libc = { version = "0.2.22", optional = true, default-features = false } | |||
# Emscripten does not support 128-bit integers, which are used by ChaCha code. | |||
# We work around this by using a different RNG. | |||
[target.'cfg(not(target_os = "emscripten"))'.dependencies] | |||
rand_chacha = { path = "rand_chacha", version = "0.2.1", default-features = false, optional = true } | |||
rand_chacha = { path = "rand_chacha", version = "0.2.2", default-features = false, optional = true } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your change will be in 0.2.3, if that's why you updated the number. (If not, well, 0.2.2 was not a mandatory upgrade.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, good catch, will fix.
@@ -61,11 +61,21 @@ impl<T> fmt::Debug for Array64<T> { | |||
write!(f, "Array64 {{}}") | |||
} | |||
} | |||
impl<T> ::core::cmp::PartialEq for Array64<T> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't care a lot. Since Edition 2018 you don't need the leading ::
.
@@ -28,6 +28,19 @@ pub struct ChaCha { | |||
pub(crate) d: vec128_storage, | |||
} | |||
|
|||
// Custom PartialEq implementation as `vec128_storage` doesn't implement it. | |||
impl ::core::cmp::PartialEq for ChaCha { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing wrong with a custom implementation, but that unsafe
code — we've been trying to minimise usage, so I'm not keen on this.
@@ -67,7 +67,7 @@ pub trait BlockRngCore { | |||
|
|||
/// Results type. This is the 'block' an RNG implementing `BlockRngCore` | |||
/// generates, which will usually be an array like `[u32; 16]`. | |||
type Results: AsRef<[Self::Item]> + AsMut<[Self::Item]> + Default; | |||
type Results: AsRef<[Self::Item]> + AsMut<[Self::Item]> + Default + PartialEq + Eq; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be okay, but need to think about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively I can conditionally do PartialEq+Eq
on BlockRng
where R::Results: PartialEq
.
src/rngs/adapter/reseeding.rs
Outdated
@@ -147,7 +147,7 @@ where | |||
{ | |||
} | |||
|
|||
#[derive(Debug)] | |||
#[derive(Debug, PartialEq, Eq)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this makes sense. We have a reseed-on-fork feature which in a sense violates Eq
(though in a new process). We also don't guarantee we won't introduce non-deterministic reseeding in the future.
We also have no use for this, since ThreadRng
shouldn't be comparable (as already mentioned)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll get rid of this then. From the description, this sounded like a wrapper class that would be comparable if the RNG it's wrapping is comparable, and the example uses a ChaCha20Core, though I'm sure there is some nuance I'm missing 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should get rid of this on both ReseedingRng
and ReseedingCore
right? Or just the latter?
This revocation is incomplete too — it uselessly derives on |
Well my GitHub newbishness is showing. Evidently I had a bunch of comments "in review" that weren't actually posted. I apologize about seeming unresponsive. |
A bitwise comparison like this is not reliable for a seekable This could be done without breaking existing impls of @dhardy, what do you think? |
You are right @kazcw, on the other hand I don't think false negatives for RNG comparisons are a big deal: if
Correctly implementing this in general is not trivial. Even with bounds on The alternative is not to implement |
Thinking about this some more: if In general, though, |
I think an impl that satisfies the definition of an equivalence relation but yields results that depend on otherwise-unobservable implementation details is likely to surprise users. I think in practice people will expect that A least-surprise |
Good points @kazcw (though the above still allows fixing false negatives in updates). So then we don't derive for /// Read remaining entries in the buffer
#[inline]
pub fn remaining_buf(&self) -> &[R::Item] {
&self.results.as_ref()[self.index..]
} (Also for |
@dhardy I'm not sure I'm fully following this discussion. If no external state is relied upon, then I don't see how two instances that are bitwise identical can diverge when performing the same operations. My use case just needs one type of PRNG that only uses its internal state to calculate the next value and is therefore deterministic, and then I'd like that PRNG to implement PartialEq/Eq. However for all PRNGs where those properties apply, I think it would make sense to implement PartialEq/Eq. For ones that rely on eternal state, I could see arguments in either direction for whether or not it makes sense for them to implement PartialEq/Eq, but I don't have an opinion there. |
No, these will test equal and not diverge. The first point is that by using seek operations, some buffered PRNGs like ChaCha may not be bitwise identical (due to buffer state) but still behave identically; ideally these should test equal but the argument above is that it doesn't matter much. The second point is that it is possible (though extremely unlikely) that two unequal PRNGs will produce the same output for the next n operations, then diverge. RNGs relying on external state ( |
Thanks for the explanation. For my purposes, bitwise identical is all that matters, but handling the buffered case differently could make sense. |
@dhardy I'm not sure what this is in reference to. Neither the impl in the PR nor my suggestion rely on comparing buffer contents or other PRNG output (the derived The issue that I'm concerned about, and elaborated on in my second comment, is that a bitwise comparison of Lets define some terms. identical is defined inductively:
equivalent is broader:
I think an implementation of
The problem is the indeterminacy in the 3rd case; a user who expects I see no benefit to the indeterminate behavior; it would not be appreciably more complex in implementation to test equivalence (using
|
It is possible for two completely different PRNGs to produce the same next word of output, and even several words (though obviously you would never except to observe an event this improbable). It has nothing to do with buffers.
We haven't yet specified exactly what happens. And I agree, we should be more precise about this (and probably not use derive on Also, if equivalent RNGs are not guaranteed to test equal, then we should probably only implement
Great. So I'm not sure why we're arguing? But your code misses one thing: it must also test results already in the buffer. Right? Or can we guarantee that this is unnecessary (since the buffer is always cleared when seeking)? So, it's probably easiest if @rickvanprim removes the code affecting |
As in rust-random#975, but defining equality such that the user is not exposed to the fact that one logical state may have different representations in an implementation-specific way.
Implements rust-random#974. As in rust-random#975, but defining equality such that the user is not exposed to the fact that one logical state may have different representations in an implementation-specific way.
Implements rust-random#974. As in rust-random#975, but defining equality such that the user is not exposed to the fact that one logical state may have different representations in an implementation-specific way.
Everything we want from here was already incorporated in #979. |
Fixes #964.