Rust doesn't want to have exceptions; it prefers return value based error handling ("monadic error handling", specifically). A lot of people seem to be under the impression that Rust's error handling model is a way of emulating exceptions -- it's not; it's a different model, and we don't want to emulate exceptions. The fact that error propagation is a common task doesn't mean that Rust's way of providing it is done to emulate exceptions.
------
In a sense, every part of Rust's error handling model is "necessary because Rust doesn't have exceptions". Similarly, every part of Java's error handling model is "necessary because Java makes it hard to do monadic error handling". Saying "X is only necessary because you don't have Y" when X is an alternative to Y isn't a very useful statement.
Basically this feature is here to provide an easy propagation of errors, something an exception system does for you for free.
"?" exist because the current error handling make it inconvenient to propagate errors, and is a workaround to make it less of a pain.
It's alright. There is no shame in it. Just say so, don't try to talk your way out of it.
That's the point, it doesn't, you pay for the exception system and the implicitness that comes with it. Your comment portrays exceptions as the default that monadic errors are playing catch-up with. I could easily do the reverse.
> is a workaround to make it less of a pain.
You say workaround, I say feature.
Ultimately, monadic errors and exceptions have tradeoffs.
Like I said, given any tradeoff between feature A and feature B, you can always say "subfeature X of A exists because we don't have B". It's not a meaningful thing to say.
I didn't avoid an answer, I genuinely think that your comment has no substance to it because it pretends that exceptions are a "default" system.
if err != nil { return err }
Comparing Rust to a language with exceptions, like Java, the only difference is that they places that the code can return is explicit rather than implicit, because of that one extra character. I find this extremely pragmatic since most of the time errors just need to be propagated up to a place where they can be handled, but you still need to know when that may happen.
* makes you be explicit about call sites which throw
* makes it easy to reify fallible computation (so it can be stored in e.g. a Future)
For instance:
fn foo() -> Result<Value, IoError> {
let x = a()?;
let _ = b();
if someCondition {
return Err(IoError::Whatever)
}
return c(x).unwrap();
}
is just Value foo() throws IOException {
x = a();
try {
b();
} catch(Exception e) { /* TODO: ERROR HANDLING? */ }
if (someCondition) {
throw new IOException();
}
try {
return c(x);
} catch(Exception e) { abort(); }
}
This is made especially clear by Swift's model which is pretty
much exactly the same as Rust's, but makes it look like exceptions: // RIP typed errors
func foo() -> Value throws {
let x = try a() // "rethrow"
try? b()
if someCondition {
throw "Oh No IO!"
}
return try! c(x)
}(My code does have an error because I forgot to wrap the result in Ok)