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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright 2017-2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Error types

use core::fmt;

#[cfg(feature="std")]
use std::error::Error as stdError;

/// Error kind which can be matched over.
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub enum ErrorKind {
/// Permanent failure: likely not recoverable without user action.
Unavailable,
/// Temporary failure: recommended to retry a few times, but may also be
/// irrecoverable.
Transient,
/// Not ready yet: recommended to try again a little later.
NotReady,
/// Uncategorised error
Other,
#[doc(hidden)]
__Nonexhaustive,
}

impl ErrorKind {
/// True if this kind of error may resolve itself on retry.
///
/// See also `should_wait()`.
pub fn should_retry(self) -> bool {
match self {
ErrorKind::Transient | ErrorKind::NotReady => true,
_ => false,
}
}

/// True if we should retry but wait before retrying
///
/// This implies `should_retry()` is true.
pub fn should_wait(self) -> bool {
self == ErrorKind::NotReady
}

/// A description of this error kind
pub fn description(self) -> &'static str {
match self {
ErrorKind::Unavailable => "permanent failure or unavailable",
ErrorKind::Transient => "transient failure",
ErrorKind::NotReady => "not ready yet",
ErrorKind::Other => "uncategorised",
ErrorKind::__Nonexhaustive => unreachable!(),
}
}
}

/// Error type of random number generators
///
/// This is a relatively simple error type, designed for compatibility with and
/// without the Rust `std` library. It embeds a "kind" code, a message (static
/// string only), and an optional chained cause (`std` only).
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
msg: &'static str,
#[cfg(feature="std")]
cause: Option<Box<stdError + Send + Sync>>,
}

impl Error {
/// Create a new instance, with specified kind and a message.
pub fn new(kind: ErrorKind, msg: &'static str) -> Self {
#[cfg(feature="std")] {
Error { kind: kind, msg: msg, cause: None }
}
#[cfg(not(feature="std"))] {
Error { kind: kind, msg: msg }
}
}

/// Create a new instance, with specified kind, message, and a
/// chained cause.
///
/// Note: `stdError` is an alias for `std::error::Error`.
///
/// If not targetting `std` (i.e. `no_std`), this function is replaced by
/// another with the same prototype, except that there are no bounds on the
/// type `E` (because both `Box` and `stdError` are unavailable), and the
/// `cause` is ignored.
#[cfg(feature="std")]
pub fn with_cause<E>(kind: ErrorKind, msg: &'static str, cause: E) -> Self
where E: Into<Box<stdError + Send + Sync>>
{
Error { kind: kind, msg: msg, cause: Some(cause.into()) }
}

/// Create a new instance, with specified kind, message, and a
/// chained cause.
///
/// In `no_std` mode the *cause* is ignored.
#[cfg(not(feature="std"))]
pub fn with_cause<E>(kind: ErrorKind, msg: &'static str, _cause: E) -> Self {
Error { kind: kind, msg: msg }
}

/// Get the error kind
pub fn kind(&self) -> ErrorKind {
self.kind
}

/// Get the error message
pub fn msg(&self) -> &'static str {
self.msg
}
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "RNG error [{}]: {}", self.kind.description(), self.msg())
}
}

#[cfg(feature="std")]
impl stdError for Error {
fn description(&self) -> &str {
self.msg
}

fn cause(&self) -> Option<&stdError> {
self.cause.as_ref().map(|e| e.as_ref() as &stdError)
}
}
55 changes: 53 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,6 @@
use core::marker;
use core::mem;
#[cfg(feature="std")] use std::cell::RefCell;
#[cfg(feature="std")] use std::io;
#[cfg(feature="std")] use std::rc::Rc;

// external rngs
Expand All @@ -265,6 +264,9 @@ pub use isaac::{IsaacRng, Isaac64Rng};
pub use chacha::ChaChaRng;
pub use prng::XorShiftRng;

// error types
pub use error::{ErrorKind, Error};

// local use declarations
#[cfg(target_pointer_width = "32")]
use prng::IsaacRng as IsaacWordRng;
Expand Down Expand Up @@ -294,6 +296,7 @@ pub mod isaac {
}

// private modules
mod error;
mod rand_impls;
mod prng;

Expand Down Expand Up @@ -449,6 +452,22 @@ pub trait Rng {
impls::fill_bytes_via_u64(self, dest)
}

/// Fill `dest` entirely with random data.
///
/// 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
/// the intended way to use external (true) RNGs, like `OsRng`. Its main
/// use-cases are to generate keys and to seed (infallible) PRNGs.
///
/// Other than error handling, this method is identical to [`fill_bytes`], and
/// has a default implementation simply wrapping [`fill_bytes`].
///
/// [`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.


/// Return a random value of a `Rand` type.
///
/// # Example
Expand Down Expand Up @@ -603,48 +622,68 @@ pub trait Rng {
}

impl<'a, R: ?Sized> Rng for &'a mut R where R: Rng {
#[inline]
fn next_u32(&mut self) -> u32 {
(**self).next_u32()
}

#[inline]
fn next_u64(&mut self) -> u64 {
(**self).next_u64()
}

#[inline]
fn next_f32(&mut self) -> f32 {
(**self).next_f32()
}

#[inline]
fn next_f64(&mut self) -> f64 {
(**self).next_f64()
}

#[inline]
fn fill_bytes(&mut self, dest: &mut [u8]) {
(**self).fill_bytes(dest)
}

#[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?

impl<R: ?Sized> Rng for Box<R> where R: Rng {
#[inline]
fn next_u32(&mut self) -> u32 {
(**self).next_u32()
}

#[inline]
fn next_u64(&mut self) -> u64 {
(**self).next_u64()
}

#[inline]
fn next_f32(&mut self) -> f32 {
(**self).next_f32()
}

#[inline]
fn next_f64(&mut self) -> f64 {
(**self).next_f64()
}

#[inline]
fn fill_bytes(&mut self, dest: &mut [u8]) {
(**self).fill_bytes(dest)
}

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

/// Iterator which will generate a stream of random items.
Expand Down Expand Up @@ -777,7 +816,7 @@ impl StdRng {
/// Reading the randomness from the OS may fail, and any error is
/// propagated via the `io::Result` return value.
#[cfg(feature="std")]
pub fn new() -> io::Result<StdRng> {
pub fn new() -> Result<StdRng, Error> {
match OsRng::new() {
Ok(mut r) => Ok(StdRng { rng: r.gen() }),
Err(e1) => {
Expand Down Expand Up @@ -807,6 +846,11 @@ impl Rng for StdRng {
fn fill_bytes(&mut self, dest: &mut [u8]) {
self.rng.fill_bytes(dest)
}

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

impl<'a> SeedableRng<&'a [usize]> for StdRng {
Expand Down Expand Up @@ -891,10 +935,12 @@ pub fn thread_rng() -> ThreadRng {

#[cfg(feature="std")]
impl Rng for ThreadRng {
#[inline]
fn next_u32(&mut self) -> u32 {
self.rng.borrow_mut().next_u32()
}

#[inline]
fn next_u64(&mut self) -> u64 {
self.rng.borrow_mut().next_u64()
}
Expand All @@ -903,6 +949,11 @@ impl Rng for ThreadRng {
fn fill_bytes(&mut self, bytes: &mut [u8]) {
self.rng.borrow_mut().fill_bytes(bytes)
}

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

/// Generates a random value using the thread-local random number generator.
Expand Down
Loading