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

Common error details, as @newpavlov's design #30

Closed
wants to merge 10 commits into from
Closed

Conversation

dhardy
Copy link
Owner

@dhardy dhardy commented Oct 31, 2017

Implement new design in #10.

Any comments @newpavlov or @pitdicker?

Error should be 32 bytes in size (two enums padded for alignment + a fat pointer). It could potentially be reduced to 24 bytes by combining the two enums, but that would be ugly. Otherwise it could only be much smaller by omitting the details altogether. Hopefully the size doesn't make too much of a difference anyway; I don't know.

Most of the "details" implementation is hidden and "kind" is still not exhaustively matchable.

Doc here

Copy link

@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.

Have you measured the type is 32 bytes?
Edit: it is.

}
write!(f, "RNG {} (details: {})",
self.kind.description(),
self.details().unwrap_or("NA"))

Choose a reason for hiding this comment

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

We maybe should not print the details here, see rust-lang-deprecated/error-chain#154 (comment)

self.cause.as_ref().map(|e| &**e)
match self.details {
ErrorDetails::Error(ref e) => Some(&**e),
// unforunately we cannot report &str cause here

Choose a reason for hiding this comment

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

I think we can if ErrorDetails where to implement std::error::Error. Or by implementing std::error::Error for ErrorDetails::Str(...)

/// Create a new instance, with a static string describing the details.
///
/// This may be used in both `std` and `no_std` enviroments.
pub fn new_str(kind: ErrorKind, details: &'static str) -> Self {

Choose a reason for hiding this comment

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

The name could be a little bit confusing. Maybe a more verbose new_with_cause_str?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Why the long name?

Choose a reason for hiding this comment

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

I agree with new_str being a bit confusing, but I personally can't come up with the better alternative, so I guess we can leave it up to docs.

Error {
kind: kind,
cause: cause,
pub fn new_err<E>(kind: ErrorKind, cause: E) -> Self

Choose a reason for hiding this comment

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

Does it make sense to have a function that takes both a kind and a string, and use one of them depending on whether the std feature is enabled? Now implementations of Rng need a std feature flag to give the right type of cause.

Copy link
Owner Author

Choose a reason for hiding this comment

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

I don't get what you mean? This is more generic than new_str and can still accept a string but is only available with the std library (we could probably depend on alloc instead, but that's another matter; we need Box).

Copy link
Owner Author

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

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

The Into where clause it neat, wouldn't have thought about that myself. Although maybe it is common, and I am just missing something :-).

I was thinking about cases like this, where a Rng implementation needs to use different functions to construct an error depending on whether the std feature is available.

Then a helper function is maybe useful:

The `Into` where clause it neat, wouldn't have thought about that myself.  Although maybe it is common, and I am just missing something :-).

``` rust
#[cfg(feature="std")]
pub fn new_with_cause(kind: ErrorKind, cause: E, details: &'static str) -> Self {
    new_str(kind, details)
}

#[cfg(not(feature="std"))]
pub fn new_with_cause(kind: ErrorKind, cause: E, details: &'static str) -> Self {
    new_err(kind, cause)
}

Copy link

@newpavlov newpavlov Oct 31, 2017

Choose a reason for hiding this comment

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

In no_std environment you simply can't have implementation of Error trait, as it's defined only in the std. So either way crate authors will have to write cfgs to handle switching.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Or authors supporting no_std can simply use new_str with a string description everywhere. There's no advantage to using new_err if the details are just a string.

Choose a reason for hiding this comment

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

I updated my branch with JitterRng to the new error handling pitdicker@b223c60.

The second commit shows what I mean pitdicker@f6b3531.
In theory an RNG implementation can support both std and no_std compatible error causes, and the choice is made in rand_core.

Choose a reason for hiding this comment

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

It was just an idea, but I don't think it helps in enough cases. Lets leave the two functions as is.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Your new_with_cause isn't even going to compile in no_std mode since it depends on std::error::Error.

I see what you mean, but I'm not convinced it's worth returning a chained Error type in this case; there is no added information (but maybe your description() strings should include all the details from the Display impl).

Choose a reason for hiding this comment

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

Oops. Ok, please forget I said anything :-)

///
/// In the case of a chained error, the actual error may be obtained via
/// `std::error::Error::cause`.
pub fn details(&self) -> Option<&str> {
Copy link

@newpavlov newpavlov Oct 31, 2017

Choose a reason for hiding this comment

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

Maybe "will be obtained"? Also not description, but cause.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Um, actually I think my description is correct, but I'll try to make it clearer.

Choose a reason for hiding this comment

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

I meant in reverse. :) In the code description() method is used, but in the docstring you write about cause.

Copy link
Owner Author

Choose a reason for hiding this comment

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

This is the details() function. Anyway, the terms are a bit confused, although description should be distinct from details/cause

ErrorKind::Other => write!(f, "An unspecified RNG error occurred."),
ErrorKind::__Nonexhaustive => unreachable!(),
}
write!(f, "RNG {}", self.kind.description())

Choose a reason for hiding this comment

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

I think "RNG Error: {}" will be better.

impl ::std::error::Error for ErrorDetails {
fn description(&self) -> &str {
match *self {
ErrorDetails::None => "NA", // case shouldn't be used

Choose a reason for hiding this comment

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

It's better to write unreachable!() here if this case indeed can not be used anywhere.

@dhardy
Copy link
Owner Author

dhardy commented Oct 31, 2017

Ok, I addressed some of your points.

I don't see much point renaming the new_* functions. As it is they are descriptive of the type of the details (str or something coercible to an Error). We could renamed to new_with_*, but that's longer for little extra clarity. We could use names like new_cause (or new_chained) and new_msg instead, but I'm not sure that's any clearer.

ErrorKind::Other => write!(f, "An unspecified RNG error occurred."),
ErrorKind::__Nonexhaustive => unreachable!(),
}
write!(f, "RNG error: {}", self.kind.description())

Choose a reason for hiding this comment

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

According to the book description is supposed to be a short description, and Display a user-facing representation of the error (a proper sentence with capital letter, dot at the end, and a bit less condensed text).

I think this is okay, but like the previous version more.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Does it? I skimmed through and saw an example similar to this, but in general the section is long and a little vague.

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.

3 participants