As far as I'm concerned, the killer feature is Result type error handling, which forces the calling code to deal with the error — as in your sample code.
Sum types have lots of other benefits, but what I really care about is having Result types instead of exceptions or returned error codes where the error handling can be omitted. Even if you choose to "unwrap" or similar, having high confidence that you can audit a codebase and at least know that every handleable error known to the APIs has been checked, that's invaluable. (Panics which represent non-handleable errors notwithstanding.)
> Result type error handling, which forces the calling code to deal with the error
That only works if you remember to deal with the Result type itself, which is something you're likely to forget to do if you prone to forgetting to handle values when they come to you.
Which means that as far as I'm concerned, the killer feature requires that the code not compile unless the error is handled. Even if the handling is just to discard the error, so long as I can audit the codebase and discover all the places where an error was discarded.
That requires a language to have a special concept for errors, which is generally considered a bad idea. The Result type itself exists to try to not introduce a special error concept in the language, but rather allow humans to build up their constructs that only makes sense to humans, rather than machines, using lower level language features.
Realistically, if you are prone to forgetting to handle the state in your application then you will be prone to forgetting to consider values of all kinds, including the Result itself. That is the achilles heel of the Result type, leaving it to be far less useful than it seems like it should be on the surface.
A language that will fail to compile if you forget to handle all the states of your application sounds wonderful, but I don't know of any languages that have even come close to solving that problem. That is an excruciatingly hard problem to solve, and maybe isn't even solvable without human-level intelligence.
warning: unused `Result` that must be used
--> src/main.rs:7:5
|
7 | foo();
| ^^^^^^
|
= note: `#[warn(unused_must_use)]` on by default
= note: this `Result` may be an `Err` variant, which should be handled
The quick fix is to change `foo();` to `foo().unwrap();`, which panics if the call returns an Err variant. But seeing `unwrap` in the code lets me know where an error may occur. That's better than the silent failure we get in many other languages when a result code is left unhandled or a potential exception isn't wrapped in a `try` block.
Yes, like I said you can bake additional features into the language, but that is beyond the topic of the Result type. Go, with some modification, could allow the same type of mistake even without Result's existence.
It doesn't have to be fully specific to the error type, you can have a "must use" attribute that can be applied to any type, and then support for that in the compiler. This allows it to be used for other things like Option as well, and anything else that should cause an error if ignored.
Certainly you can bake monadic-type behaviour into the language, but at that point the Result type is superfluous and provides a weaker interface than what language constructs could provide on their own.
Sum types have lots of other benefits, but what I really care about is having Result types instead of exceptions or returned error codes where the error handling can be omitted. Even if you choose to "unwrap" or similar, having high confidence that you can audit a codebase and at least know that every handleable error known to the APIs has been checked, that's invaluable. (Panics which represent non-handleable errors notwithstanding.)