4 ms·
As a Go developer, I'm curious how is the error handled when foo() or bar() fail? Could you show an example where `bar()` returns a re-triable error or a fatal
by zalmoxes 10y ago
As a Go developer, I'm curious how is the error handled when foo() or bar() fail?
Could you show an example where `bar()` returns a re-triable error or a fatal error etc? something where I have to make a decision other than returning it up the call stack?.
- Dobbs 10y ago`try!(foo)` is roughly equivalent to this (invalid) code: foo().unwrap_or_else({ |e| return Err(e) }) Or in Go terms: a := try!(foo) is equivalent to: a, err := foo() if err != nil { return nil, err } Basically it will take the result of foo, and if foo returns an error it will return the error itself. https://doc.rust-lang.org/beta/std/result/ https://doc.rust-lang.org/beta/std/result/ has lots of examples of alternative ways you can deal with the error case, or the Ok() case of a result.
- zphds 10y agoIt's worth mentioning that try! is a macro that basically takes that expr and turns it to something similar to your go example.
- steveklabnik 10y agoWhen they fail, it's like "return err"; that is, they just return the error value. You can only make it return up the call stack, that's it. It's for recoverable errors, not fatal ones. And the calling function would check for the error case and re-try if that's the behavior it wants.
- tsuraan 10y agoSo do all the functions have to have the same error type as the function that is calling them? I don't know rust, but in haskell notation, if a function returns "Either x y" would it then only be able to use the "?" notation on functions that themselves return "Either x z", and not on functions that return "Either a b" (so, failure sides have to match, but success sides are flexible)? EDIT: Answering my own question: https://github.com/rust-lang/rfcs/blob/master/text/0243-trait-based-exception-handling.md https://github.com/rust-lang/rfcs/blob/master/text/0243-trai... makes it pretty clear; the error types must be the same. Which completely makes sense.
- steveklabnik 10y agoThey have to be convertible to the type that's being returned. You'd need an implementation of y -> z. It will call the conversion function for you. use std::fs::File; enum MyError { OhNo(String), } fn foo() -> Result<File, MyError> { Ok(File::open("foo.txt")?) } With only the above code, we get an error: the trait bound `MyError: std::convert::From<std::io::Error>` is not satisfied So by implementing it... use std::convert::From; use std::io; impl From<io::Error> for MyError { fn from(e: io::Error) -> MyError { // do some sort of conversion MyError::OhNo(String::from("some error")) } } Now `?` does the coercion automatically.
- tomtomtom777 10y agoThis is one of the things that makes Rust's error handling so elegant. The error "handling" code mostly part of the error types themselves, mostly in the form of to/from conversions. The actual logic code can then rely on these conversions and is freed from bloat.
- masklinn 10y ago> So do all the functions have to have the same error type as the function that is calling them? Not quite, they must have an error type convertible to the caller's error type, try! (and ?) really desugar to match val { Ok(v) => v, Err(e) => return Err(From::from(e)) } From is a generic conversion trait, a type A can implement From<B> in which case `From::from(b: B)` will yield an A (assuming A is either inferred or explicitly requested)
- Perceptes 10y agoThe ? operator is a blanket "return from the current function if error, otherwise produce successful value." If you wanted to have conditional logic to handle the error within the current function, you'd use a match: let foo_ok = foo()?; let bar_ok = match foo_ok.bar() { Ok(bar_ok) => {} // conditional logic here Err(bar_err) => return Err(bar_err), }; bar_ok.baz()
- vvanders 10y agoTo extend on this there's also a ton of great error mapping/handling functions as a part of Result. https://doc.rust-lang.org/book/error-handling.html https://doc.rust-lang.org/book/error-handling.html goes into it in great depth. It's a fair bit to grok but really shows how thorough the options Rust has when dealing with errors.
- fizzbatter 10y agoIf you need more complex logic, you simply wouldn't use `try!()`. Try! is intended for those `if error then return` checks, just like with Go's `if err != nil { return err }`. If the error needs to be checked for a specific type (like a timeout) and then retried X times, you don't use the simple `if err != nil {...}`, you hand write something: let barRes = try!(foo()).bar(); match barRes { Err(MyError::Timeout(_)) => // .. some retry code } Note that the above code is woefully incomplete (i'm not checking all possible match values, for example), but it should give you the gist of the idea. edit: Man, the guy (or wo-guy) has a problem and we all pounce :)
- masklinn 10y ago> As a Go developer, I'm curious how is the error handled when foo() or bar() fail? By converting their error to whatever the enclosing function returns, and returning. > Could you show an example where `bar()` returns a re-triable error or a fatal error etc? You'd use either one of the various combinators[0] or a full explicit `match` (which is what try!, ? and the combinators end up desugaring to). So let's say you wanted special handling for bar()'s result, you'd specially handle that one: let a = foo()?; let b = match a.bar() { Ok(value) => { // the call succeeded } Err(error) => { // the call failed } } etc… If you want bar() to be a fatal error, you can unwrap(), that desugars to: match v { Ok(v) => v, Err(err) => panic!("called `Result::unwrap()` on an `Err` value: {:?}", err) } that is it panics on failure and unwraps the value on success (there's also an `unwrap_err` which does the opposite) [0] https://doc.rust-lang.org/std/result/enum.Result.html#method.map https://doc.rust-lang.org/std/result/enum.Result.html#method...