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

New error handling + type #225

Merged
merged 5 commits into from
Jan 10, 2018
Merged

New error handling + type #225

merged 5 commits into from
Jan 10, 2018

Conversation

dhardy
Copy link
Member

@dhardy dhardy commented Jan 8, 2018

This brings in the new error type and handling from dhardy/master. See dhardy#10.

It also adds try_fill_bytes; see dhardy#12 and dhardy#27. (There's still a question on naming here.)

fill_via_try_fill includes some code for implicit error handling; see dhardy#25.

Finally, support for NaCl is removed from OsRng; see dhardy#19.

Note that for now all Rng methods besides next_u32 have default implementations; this may change before 0.5 but not in this PR.

Also note that the new error module is deliberately private; probably it will move to rand-core, however the names publically exported in rand will still be exported there.

@dhardy dhardy mentioned this pull request Jan 8, 2018
Copy link
Contributor

@pitdicker pitdicker left a comment

Choose a reason for hiding this comment

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

Looks good! I had some minor comments.

I see you basically copied the entire os.rs file, and created a new error module. Nice.

src/impls.rs Outdated
@@ -167,4 +167,40 @@ pub fn next_u64_via_fill<R: Rng+?Sized>(rng: &mut R) -> u64 {
impl_uint_from_fill!(rng, u64, 8)
}

/// Implement `fill_bytes` via `try_fill` with implicit error handling.
pub fn fill_via_try_fill<R: Rng+?Sized>(rng: &mut R, dest: &mut [u8]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I still feel a bit uneasy about this function. We had to pick some defaults for how many times to retry or how long to wait in case of an error, that may not suite every RNG.

What do you think about moving it to os.rs and not making it public?

Copy link
Member Author

Choose a reason for hiding this comment

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

At the moment it's not public. I see your point though, we could simply force every use-case to have its own custom implementation instead of having this one. I'm not sure.

/// [`fill_bytes`]: trait.Rng.html#method.fill_bytes
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
Ok(self.fill_bytes(dest))
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Just as a test I removed the default implementation, and you did not miss to implement it for any wrappers etc. 👍.

I would like to see the default implementations be removed, but good to wait until all the changes for RNG implementations are merged.

#[inline]
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
(**self).try_fill_bytes(dest)
}
}

#[cfg(feature="std")]
Copy link
Contributor

Choose a reason for hiding this comment

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

Not part of this PR, but should this also be available with the alloc feature?

Copy link
Contributor

Choose a reason for hiding this comment

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

With that I mean impl<R: ?Sized> Rng for Box<R> where R: Rng {

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah. Probably. Make a separate PR?

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay. But I should probably wait until this PR has landed because they will conflict?

src/os.rs Outdated
}
fn next_u32(&mut self) -> u32 {
// note: `next_u32_via_fill` does a byte-swap on big-endian
// architectures, which is not really needed here
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it was me who wrote this comment? I don't really think it matters, the system call and OS RNG should dwarf the time the byte swap takes.

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought it was me. But yes, I don't think it's important, so the comment could be removed.

Copy link
Member Author

Choose a reason for hiding this comment

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

On that topic, impl_uint_from_fill does not call .to_le(). It should, surely?

Copy link
Contributor

@pitdicker pitdicker Jan 8, 2018

Choose a reason for hiding this comment

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

He, your right! While the documentations says it uses little-endian order. Really need some tests there 😄. Edit: someday though

@@ -46,34 +47,24 @@ impl<R: Read> ReadRng<R> {

impl<R: Read> Rng for ReadRng<R> {
fn next_u32(&mut self) -> u32 {
// This is designed for speed: reading a LE integer on a LE
// platform just involves blitting the bytes into the memory
// of the u32, similarly for BE on BE; avoiding byteswapping.
Copy link
Contributor

Choose a reason for hiding this comment

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

This is probably a breaking change on big-endian? I don't think this will be a problem for anyone, but maybe good to mention.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, but only because it wasn't portable before. I doubt anyone was depending on the old non-portable behaviour.

src/os.rs Outdated
panic!("kern.arandom sysctl failed! (returned {}, s.len() {}, oldlenp {})",
ret, s.len(), s_len);
// Old format string:
// "kern.arandom sysctl failed! (returned {}, s.len() {}, oldlenp {})"
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we care about this?

@pitdicker
Copy link
Contributor

pitdicker commented Jan 8, 2018

It also adds try_fill_bytes; see dhardy#12 and dhardy#27. (There's still a question on naming here.)

I think I would go with renaming.

For RNG implementations we are going to have breaking changes anyway, so no problem in renaming there. For users of fill_bytes they would have to rename to either fill or try_fill. And I see some use in that: code that now has no choice but to panic in case of an error, is now reminded it can switch to try_fill thanks to a compiler error.

Related thought: it would be great if we had some function that could fill slices of any type. Like the from_rng code for ISAAC and HC-128 Xorshift do for integers, and like the current implementation of Rand for arrays of other types.

@dhardy
Copy link
Member Author

dhardy commented Jan 8, 2018

I think I agree about the renaming fill_bytesfill forcing users to update.

Not sure what you mean about filling slices?

Travis points out something about Rustc 1.15, though very easy to work around:

error: struct field shorthands are unstable

src/lib.rs Outdated
/// Fill `dest` entirely with random data.
///
/// Implementations of this trait must implement at least one of
/// `next_u32`, `next_u64`, `fill` and `try_fill` directly. In the case this
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is true, only next_u32 has to be implemented? But this part of the documentation is not going to live long anyway.

src/lib.rs Outdated
/// This is the only method which allows an RNG to report errors while
/// generating random data; other methods either handle the error
/// internally or panic. This method is intended to allow error-handling
/// when seeding keys or PRNGs from external (true) RNGs.
Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry for being picky... "This method is intended to" sounds a bit specific. Maybe "This method can be used to"?

Copy link
Member Author

Choose a reason for hiding this comment

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

It is specific, and intended to explain why there isn't try_next_u64 etc. You think it's over-specific? I mean it's obvious there may be other ways to use it, but this is the only important one I see.

Copy link
Contributor

Choose a reason for hiding this comment

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

That is a good intention, useful to add. Could you reword it a little, because I missed it?
Something like

This method is intended to be the way to use external (true) RNGs, like OsRng. You primarily want to use it to seed keys or (infallible) PRNGs.

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay, done. I'll rebase later, to let you review changes now.

Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure what there is left to rebase or review?

@pitdicker
Copy link
Contributor

Not sure what you mean about filling slices?

It was just a thought. Can we provide a function that can fill slices of any size, not just arrays of at most 32 elements as gen does now? And when the slice contains just integers, can that make use of fill, instead of generating integers one by one?

I have something working, but not very ergonomic: https://gist.github.com/pitdicker/ba4c08bf407c04a6f2a63a8116afd4d1

Don't let it hold up this PR though.

@dhardy
Copy link
Member Author

dhardy commented Jan 8, 2018

Interesting idea. Nothing to do with this PR though as I see it.

@pitdicker
Copy link
Contributor

Having a method that can fill slices of multiple types could be an argument to rename fill_bytes or not. We could point to it as a maybe more ergonomic alternative to make the breakage a little easier to accept. Or it could be an argument not to rename fill_bytes to fill, because that name would be better for a generic method.

But I like the rename here and think it is the right choice. I will not distract this PR further.

@dhardy
Copy link
Member Author

dhardy commented Jan 9, 2018

Sorry, I see what you mean now: replace fill with a generic function filling any slice type.

I don't know. The reason we switched key types to byte arrays was to avoid Endianness issues. I guess we could even go back to u32 / u64 arrays and do all the conversions in a fill function. I'm not so sure about it though.

@pitdicker
Copy link
Contributor

I am really all for keeping the Rng trait as we have now, and to not complicate the fill method for RNG implementations.

I think a more generic method can be useful for rand. But at least my try depends on the distributions to provide default implementations for floats and bool.

And the only overhead for integer slices of having the u8 Rng::fill method in between something more generic is having an unaligned copy instead of an aligned copy if directly filling is not possible.

@dhardy
Copy link
Member Author

dhardy commented Jan 9, 2018

A generic fill also feels out of place here: Rng is about what RNG sources need to provide; Sample: Rng and Distribution are about generating various types from an RNG.

Of course, you can't generated an unsized type like a slice directly, so maybe Sample should have a fill function in addition to gen? In that case it would make more sense to use fill_bytes here.

@dhardy
Copy link
Member Author

dhardy commented Jan 9, 2018

Rebased, but I think I'll remove the last two renaming commits; now I've re-read the comments I see @burdges had the same idea back in October. It also avoids unnecessary breakage.

@pitdicker
Copy link
Contributor

We can still have fill_slice as a possible name for a more generic function. You have changed your mind about the argument that it helps users to pick between a fallible and infallible method?

@dhardy
Copy link
Member Author

dhardy commented Jan 9, 2018

To some extent, yes, because the way most people handle errors is not much better than unwrap() and worse than the implicit handling we have for some OsRng errors.

BTW I revised that handling as you suggested (see last commit).

@pitdicker
Copy link
Contributor

I hope users of try_fill/try_fill_bytes will pass on the error with ?. It should not generally be part of some inner loop like next_u32 or next_u64. But I will leave the decision to you 😄.

Then the only open thing left is the endianness in impl_uint_from_fill, or do you want to do that as part of another PR?

@dhardy
Copy link
Member Author

dhardy commented Jan 9, 2018

I have a TODO note on the Endianness. It wasn't introduced in this PR so I won't fix here.

Since we deliberately report NotReady errors just passing the error on with ? isn't always appropriate.

@pitdicker
Copy link
Contributor

Feel free to merge (from me) when you think it is the right time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants