I think biggest thing it is missing is any kind of Google commitment on its long term usage.
Checked errors, via results or exceptions have never been the problem. It has always been Java the language that hasn't provided the syntax sugar for handling checked errors effectively.
There is no difference between:
A doIt() throws B
fun doIt(): Result<A, B>
It all comes down to what the language lets you do once you encounter that error.Also, external crates like "anyhow" are required if you don't want to account for literally every single possible error case. Really seems like a pedantic's dream but a burden to everyone else.
Effective Java recommends checked exceptions only in the case where the caller may recover, but in practice you're usually just propagating the error to the caller in some form, so almost everything just becomes unchecked runtime exceptions.
The issue really just arises with Java not giving you the capability to uncheck that error easily if you can't recover from it. For example, you need a ton of lines to uncheck:
A doIt() throws B {
throw new B();
}
void usingIt() {
A a;
try {
a = doIt();
} catch (B b) {
throw new RuntimeException(b);
}
a.woohoo();
}
My ideal situation would be for some sort of throws unchecked operator (or whatever syntax we want to bikeshed over) that turns them into unchecked exceptions. void usingIt() throws unchecked B {
var a = doIt();
a.woohoo();
}That's essentially the direction of modern programming languages. It's part of that feeling you get when programming haskell. Once you get it running, it just works. It's very different from the old paradigm where once you get it working, it can crash and you have to debug and do more to get it working better.
- Unwinds the stack to a try/catch or exception handler, making exceptions practically difficult to deal with in concurrent programming.
- If unchecked, can be ignored, silently propagating during stack unwinding.
- If checked, infects the call stack with 'throws' annotations.
The second is a normal return value, with no try/catch needed, handling the error case is mandatory in order to handle the success case, and there is not a separate execution regime occurring whenever an error case is encountered.
- In concurrent programming uncaught exceptions won’t leave the future. Both values are available to you just like Results. I also don’t think arguments for concurrent programming are valid though. 99% of all code is single threaded.
- It is checked.
- Result infects the call stack as well.
- handling the error case with a checked exception is also mandatory with handling the success case. There is no separate “execution regime”. What is the difference here:
val a = try {
a();
} catch (b) {
onB();
}
val a = match (a()) {
Success(a) => a
Failure(b) => onB()
} val a = a();
And start the stack unwinding. The `try/catch` is not mandatory to call a().So much so that I ended up writing a queueing app for scheduling batches of sequential tasks on the server in Dart just to see how it could work as a NodeJS replacement, and thought the whole dev experience was great.
kmp exposes everything as obj-c meaning c headers and not very good type annotations (enums are int only so you cant have full checking on each switch, everything is obj-c reference semantics meaning multi-threading gets tricky, kotlin exceptions are not catchable from swift) so there are a lot of edge cases to write unit tests for (on the client side) which negates a lot point of using kmp, and thats in addition to all of the kotlin-isms that leak out...
> Why wouldn't they at least generate enums!
they do but its going through objective-c which inherits c enums (which are basically integers) so when you use it from swift its like switching over an unbounded set so you always have to handle "default" cases leading to bugs compared to usage from android