-
Notifications
You must be signed in to change notification settings - Fork 2
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
Error handling: Error type #10
Comments
Maybe a third choice:
They all have advantages and disadvantages. A simple kind enum is easiest to match against. But the other two options would work similar to 'kind enum + cause' can take easy part in error chaining. The disadvantage is that the error type is different in 'kind enum + OS code' can take part in error chaining, but only for errors like those of 'kind enum + OS code' has my preference for now. If we go with this option, I have been toying with the thought that is should maybe be a very small separate crate. It would have to reimplement small parts of |
Another thing we considered was We could manually map OS codes to static strings, or do the dodgy thing I suggested before, potentially leaking memory. Or we could do kind + cause where cause is the wrapped error in But how useful is the cause really? In theory kind alone should include enough information most of the time? I guess the answer is that provided the context of the error isn't lost, the kind alone should be enough to localise the error — but extra details might still help. |
I didn't think this was a serious option. It seems practical. But it is not very clean, and it would set a bad precedent for other crates. I think it would be acceptable for a small crate or a private application, bud not for something as popular as
For But for someone trying to figure out why
The problem is, I don't care very strongly about returning a cause. But with |
Ditto, I feel like we could do without a cause, but it's probably worth including anyway. I updated the relevant section of the RFC. |
A somewhat related question: do we want to provide |
|
Than we do not really have a reason to return an OS error as cause in |
I now like your proposal in the RFC:
I don't like it that the error type changes, but it is practical. And that is wat For B.t.w. is it common for the fields to be public? It sure is convenient, and I see no disadvantage. |
Common? Not very since it prevents internals from being changed. But I don't think these error types need to change — in fact I think it's quite useful for users to know exactly what the type is. I'm not 100% convinced on this proposal myself but don't see anything better really. |
Quite unfortunately proposed solution will not work, lets say we have Err(rand_core::Error{kind: ErrorKind::Foo, cause: Ok("error cause")}) And now somewhere in the downstream dependency we enable One solution which comes to mind is something like this: Click to expand#[derive(Debug)]
enum ErrorDetails {
Static(&'static str),
#[cfg(feature="std")]
Error(Box<std::error::Error + Send + Sync>),
None,
}
impl ErrorDetails {
fn description(&self) -> &str {
match self {
&ErrorDetails::Static(s) => s,
#[cfg(feature="std")]
&ErrorDetails::Error(ref e) => e.description(),
&ErrorDetails::None => "None", // empty string or "NA"?
}
}
#[cfg(feature="std")]
fn cause(&self) -> Option<&std::error::Error> {
match self {
&ErrorDetails::Error(ref e) => Some(e.as_ref()),
_ => None,
}
}
}
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
details: ErrorDetails,
}
// method and parameter names probably should be renamed
impl Error {
pub fn from_kind(kind: ErrorKind) -> Self {
Self { kind, details: ErrorDetails::None }
}
pub fn from_static_str(description: &'static str, kind: ErrorKind) -> Self {
Self { kind, details: ErrorDetails::Static(description) }
}
#[cfg(feature="std")]
pub fn from_error<E>(err: E, kind: ErrorKind) -> Self
where E: Into<Box<std::error::Error + Send + Sync>>
{
Self { kind, details: ErrorDetails::Error(err.into()) }
}
pub fn kind(&self) -> ErrorKind {
self.kind
}
pub fn description(&self) -> &str {
self.details.description()
}
}
impl fmt::Display for Error {
...
}
#[cfg(feature="std")]
impl std::error::Error for Error {
...
} Here we hide details enum behind methods, thus removing problem of potential non-exhaustive match in P.S.: Also note additional |
I've forgotten my thoughts on this issue, but I'd avoid those messy method names by using |
Hm, interesting workaround, but I am afraid it's too unorthodox. I feel we better just expose opaque wrapper around |
Part of the idea of the previous Still, @newpavlov's point about wanting to make compatible types may be enough justification to do this. I'm just not sure whether it's better to have struct Error {
kind: enum { .. },
cause: enum {
Str(&'static str),
#[cfg(feature="std")]
Error(Box<std::error::Error + Send + Sync>),
None
},
} as above, or struct Error {
kind: enum { .. },
msg: Option<&'static str>,
#[cfg(feature="std")]
cause: Option<Box<std::error::Error + Send + Sync>>,
} |
I like the first type more, as it's better conveys intentions behind it. Also it has a smaller memory footprint as well. Although I am not fully sure how everything gets layouted in the memory, but I think we shouldn't care about it. :) UPD: Unfortunately I couldn't find a way to convert |
Ok, PR #32 (& comments on #30) make me think some more about this. Kind + explanation + chained causeError chaining is there to give a good explanation of what's going on; e.g.
So implementing a custom error type which can explain the current error (reseeding failed) and encapsulate the cause makes sense. The So should we (1) revise Benchmark: switching So I think the second error type above may be better; then custom error types like this are not needed. I'm also wondering about adding a common trait like this, to allow more uniform // Don't derive std Error directly because of no_std
pub trait ErrorTrait {
fn kind(&self) -> ErrorKind;
fn description(&self) -> &str;
fn cause(&self) -> Option<&ErrorTrait>;
}
#[cfg(feature="std")]
impl ::std::error::Error for ErrorTrait { .. } Kind when chainingAnother thing #32 made me think about: what should kind mean given chained errors? Presumably, unless the error can be handled somehow (e.g. reporting |
I really don't think that we should care about chaining, especially considering that error chains will be quite shallow. And introducing our own error trait feels wrong too. In other words, I think In my understanding |
On investigation into Within
Also, in |
It honestly looks like |
There's no double boxing there. There is unnecessary boxing (static strings must be boxed), so, yes, the What I find unnecessary is that bundling a message and a cause requires a new error type (and double boxing: |
I think we're saying the same thing :
|
Oh, no, I missed that |
Personally I really like the design from #30. It is a wrapper around two RNGs, and ideally should pass on the error of one, and a 'reseeding error' with the error of the other as a cause. The alternative to #32 is to add a
I would make the wrapper as transparent as possible, and not wrap the error. If it was up to me, my vote for keeping the current design from #30! |
By current design from #30 do you mean the one I implemented yesterday or the first one introduced by the PR? There's a significant change. IMO the purpose of "kind" is distinctly different from description: the description is to tell users what happened, and the kind is there to guide handling. Of course, |
There is also the new failure crate that seems to have the backing of the core team. Perhaps it suffices to use that? |
@Pratyush |
Perhaps. It might also be too early to move to that. It's nice, however, that the design of that crate includes |
No, |
From what I understand |
No, we very much need an I suppose we could still implement support for the |
Approach implemented in the #34 has a big issue: #[cfg(not(feature="std"))]
pub fn new<E>(kind: ErrorKind, msg: &'static str, _cause: Option<E>) -> Self {
Self { kind, msg }
} If you'll try to write |
@newpavlov oh dear, I was premature with that. So I guess we need two functions: fn new(kind: ErrorKind, msg: &'static str) -> Self
fn new_with_cause<E>(kind: ErrorKind, msg: &'static str, cause: E) -> Self I pushed a commit directly (since this was fairly simple). I'm not so keen on the long name |
This is about design of the error type; see also #8 (should we just panic?) and #9 (error kinds).
(This assumes we do return a
Result
somewhere and don't do all handling via panics.)Possible error types:
std
only]&'static str
— also not a good choice; see belowRustdoc: https://dhardy.github.io/rand/rand_core/struct.Error.html
New proposal, which tries to be uniform between
std
andno_std
(aside from missing cause), and allow documentation of both the current error and its cause without creating a new type.Previous proposal:
The text was updated successfully, but these errors were encountered: