-
Notifications
You must be signed in to change notification settings - Fork 469
Use failure #223
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
Use failure #223
Conversation
9364022 to
99c8b22
Compare
99c8b22 to
5ee7904
Compare
|
friendly (and last) ping. |
torkleyy
left a comment
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.
Looks good to me
|
I personally find this convention of grouping all possible errors that might occur within a library into a single The reason for this is that single monolithic error types make it difficult to determine exactly what errors might occur when calling a specific function. E.g. I originally attempted to represent all errors that might occur within the coreaudio crate with a single error type, but as a result it has been really difficult to determine what errors might actually occur and to translate them to the relevant errors in CPAL in the coreaudio backend. (It's hard to remedy in this case though when even Apple won't document exactly what errors might occur). By having unique errors per method and function call as necessary it is clearer to see what errors might actually occur just by looking at the API. It is nice to be able to match on an error enum and specifically handle each possible error exhaustively, knowing that these are all the things that might go wrong when making a specific call. When errors are all grouped into a single type, we lose this benefit of rust in that we end up having to ignore a large set of errors that can never happen when calling certain methods, most often with a wildcard. Then, if new error kinds are added in updates that might actually happen, the compiler can't help us with its exhaustiveness check as we have a wildcard ignoring or propagating the new error along with the rest of the error kinds that can't actually occur in this call. All this said, I totally agree that CPAL's error handling needs a lot of attention! The benefits you mention in your OP certainly seem appealing - I just wanted to voice my concern about the the monolithic error approach. It obviously has a lot of precedence with |
|
@mitchmindtree I agree that having specific error types is often helpful, especially when you need to handle the error. Thus, the return type of API functions should be as specific as possible. However, for the user it might be that they don't or don't directly handle it, but just cancel the function and log it at a later point. In that situation a combined error type is convenient and actually improves the type safety as the alternative wouls be boxing it up into a trait object. TL;DR: use specific return types in the library, expose the combined type just for the user. That's worked very good in Specs for me. |
|
My main motivation for this PR was to avoid panics that I was getting at present. However, I agree that it can be useful to know exactly what errors you may get from a particular function. I think there are 2 approaches we could take
OK, that's 3, but you get the idea.
I think this is the way it is so that io::Error can be cross-platform. Most of the standard library uses specialised error types restricted to only errors that may actually occur. |
| [dependencies] | ||
| lazy_static = "0.2" | ||
| failure = "0.1" | ||
| failure_derive = "0.1" |
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.
Note that failure_derive is reexported from failure and not needed as a direct dependency.
| #![recursion_limit = "512"] | ||
|
|
||
| #[macro_use] | ||
| extern crate failure_derive; |
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.
Just remove this line and use the macro from failure.
| SND_PCM_FORMAT_S32_LE, | ||
| SND_PCM_FORMAT_S32_BE, | ||
| SND_PCM_FORMAT_U32_LE, | ||
| SND_PCM_FORMAT_U32_BE,*/ |
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.
Could these reformatting changes be removed from this PR and made in a separate one? It makes reviewing the changes here much harder.
| -16 /* determined empirically */ => return Err(FormatsEnumerationError::DeviceNotAvailable), | ||
| e => check_errors(e).expect("device not available") | ||
| } | ||
| )).map_err(err_msg).context(ErrorKind::DeviceNotAvailable)?; |
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.
Style: IMO, imported top-level functions are best used scoped. Here, I think failure::err_msg would be better.
| &mut min_rate, | ||
| ptr::null_mut())) | ||
| .expect("unable to get minimum supported rete"); | ||
| .map_err(err_msg) |
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. It seems to me that near every check_errors is followed by map_err. Maybe it can just return Result<(), AlsaError> (AlsaError being a new type)?
|
I've been looking at this again. I think that failure is probably the wrong approach. I've come to realize that often I've been looking over the alsa backend and I think there's scope to improve the error handling a lot - removing some of the panics/unwraps/expects. You can write the #[inline]
fn check_errors(errno: libc::c_int) -> io::Result<()> {
if errno < 0 {
Err(err_from_code(errno))
} else {
Ok(())
}
}
/// Convert error code to io::Error (from alsa source)
// This is on the slow path, so maybe mark to avoid inlining.
fn err_from_code(errnum: libc::c_int) -> io::Error {
use std::ffi::CStr;
let errnum = errnum.abs();
match io::Error::from_raw_os_error(errnum).kind() {
io::ErrorKind::Other => {
// If we don't recognise the error code, use strerror from alsa
let msg = unsafe {
CStr::from_ptr(alsa::snd_strerror(errnum))
.to_str()
.unwrap_or("Invalid utf-8 in error message")
};
io::Error::new(io::ErrorKind::Other, msg.to_owned())
},
kind => kind.into(),
}
}and structure the error nicely as an Basically, I don't want to do any work on this unless it's likely to be accepted, so what do people think of the idea? |
|
I think |
|
I agree. I'd still like to do some work on better error reporting, converting unwrap and expect into io::Errors. In alsa error codes basically correspond to Regarding extra info, there are some debug utilities in alsa that give you info on what your current config is etc. We could perhaps have a All this would be incredibly useful to me - I still cannot use cpal because the What do you think about this? Would you accept a PR along these lines? |
|
I'm just another contributor, so I'm not the one to answer the last question. However, I don't think it should be a feature flag. If the |
|
Your debug idea is better because there is some allocation involved. I might make up a PR just for demonstration processes. I'm not sure how this crate is maintained now, or who the maintainers are. |
I had an issue with alsa recently where I had updated my computer but was using an old kernel, so trying to load a
.sofailed, and cpal panicked. I've done an example of how using failure might work with cpal here. If I was using this PR's version of cpal, I would have got info on the specific IO error, and moreover could have chosen to continue without sound (without using catch_unwind).I hope you'll consider this PR, or another similar one.