It only works when your function returns error of the same type as expression inside 'try!' or '?' macros. So in a lot of cases it doesn't work or forces programmer to return underlying type of error, instead of error which belongs to current level of visibility (in other words, it provokes programmer to violate encapsulation for the sake of lazyness). Also it means we have 2 ways to do same thing and users have to know this small trick even to read code (so it raises entry level).
I love Rust language and feel sorry for this criticism. But small things are important.
> It only works when your function returns error of the same type as expression inside 'try!' or '?' macros.
The language has actually thought of this case -- it turns out you can map lower-level errors into your higher-level error type by implementing the `From<E>` trait. E.g.:
use std::io;
pub enum MyErr { Io(io::Error), Custom(String) }
impl From<io::Error> for MyErr {
fn from(err: io::Error) -> MyErr { MyErr::Io(err) }
}
fn do_stuff() -> Result<(), MyErr> {
let f = try!(File::open(...));
// ...
}
Or you can call map_err to do an on-the-spot transformation if you don't have a general way to go from Error A to Error B.
I still sometimes make mistakes reading rust code with the ? at the end of the line, but it's just a question of what type is it and does the line return. Both of which are handled by the compiler in the end, so you get a little bit of extra knowledge that even if you misread the line of code, there's often limits to how much damage you can cause.
The error-chain [0] crate helps to reduce this boilerplate. I usually don't like macro magic like this, but the code is so straightforward that you can trust the macro to work as expected.
Well, usual pattern matching will be even shorter, so I'm talking exactly about cases when lazyness provokes programmer just return that underlying type than write pattern matching or implement trait.
It would still be true with a simple error code. It's always easier to reuse an existing abstraction (in this case, existing set of codes) than it is to define a new one.
You definitely need to use the `From` trait if you haven't already. I know others have already recommended this, but it cannot be encouraged enough. It's a great pattern for idiomatic error handling--much better than matching or `map_err`.
Maybe you already know this, but if not (and for others that don't), try it immediately. You'll be glad you did.
I know this and I don't like this. Every level of abstraction should have own errors, without dependencies on some other errors, otherwise code will be too fragile. And it's not theoretical assumption, but lessons from practice.
And if you don't trust me in this question, you can try to read Uncle Bob. I thought it's not applicable to Rust (because initially it was about OOP), but on one of refactorings I got a lot of broken dependencies in From trait. Then I realized good rules worth to be remembered.
>It only works when your function returns error of the same type as expression inside 'try!' or '?' macros. So in a lot of cases it doesn't work or forces programmer to return underlying type of error
Or you can implement a conversion from the underlying error to your custom error type[1], and 'try'/'?' will just work.
Please see reply in nearby comment (to follow DRY principle :)), and I want to note that second reason is also important: a lot of people are scared already by initial complexity of Rust and Rust's syntax. It's a mistake, because after couple of months Rust looks not more difficult than PHP or JavaScript (but gives much more satisfaction). So less quirks Rust will have - more readable and predictable code will be for newcomers.
I love Rust language and feel sorry for this criticism. But small things are important.