foo()?.bar()?.baz()?
This is somehow less clear, and somehow "more debuggable" than a, err := foo()
if err != nil {
nil, err
}
b, err := a.bar()
if err != nil {
nil, err
}
c, err := b.baz()
if err != nil {
nil, err
}
return c, nil
One of these obviously has no bugs. There is literally nothing to debug. The other has no obvious bugs.Oh, except look again, I accidentally forgot to return from those error-handling-statements and now I have what should have been a completely avoidable bug.
Ask me how many times I've been bitten by this.
It doesn't even compile so I doubt you have run into it in real code.
let a = foo()?;
let b = a.bar()?;
let c = b.baz()?;
if that's what you want, not sure how the Go version would be easier to debug beyond that, and you can easily perform that transformation when you actually need to debug that code if the "pointfree" version hinders that.I also sometimes include log statements in specific error branches for debugging and delete them afterwards. That's trivially possible in the Go version but requires more effort in the shorter variants (e.g. also if the funcitons throw exceptions instead of returning errors in other languages).
It takes more effort to wrap a handler for a specific exception because there was no need originally to write a handler in that place.
You can just macroexpand ? and do that (although I don't know that there are tools doing macroexpansion currently).
> I don't know what ? really does
It's really nothing special, `a?` simply expands to:
match a {
Ok(val) => val,
Err(err) => {
return Err(From::from(err))
}
}
> also if the funcitons throw exceptions instead of returning errors in other languages?
Ok, that's what I expected
> ?
I meant if I have foo().bar() in C# or Java and foo() throws it's equally hard to change the code to add error handling in between since I would need to add a try/catch clause and rethrow the error.
Because you would rather pay the ongoing cost of writing (and worse, having to read) thousands of pointless `if err != nil` blocks than take three minutes to see exactly what try! does, which is (almost) exactly the Rust equivalent of just that.
> That's trivially possible in the Go version
It's also trivially possible in the Rust version, except you don't pay a ludicrous cost in readability of the logic flow in the thousands of other places you have to return an error.
You can just map your logging in between:
foo().map_err(|e| {log(e); e}).bar()
Great tip. Thank you for the hint.