News for hackers. Stuff that matters.
Re:Sshole by hedora on Thursday November 05, 2026 @11:15AM (#50380958)
Exceptions are almost never the right choice. They create extremely complicated control flow extremely quickly, and then you have to test all the different control flows. Java software is an example of how this goes sideways fast. There's no way to create a class that prevents callers from reading the object before the constructor runs (in multi-threaded software, new first hands a reference to the caller, which can put it on the heap, then it calls the constructor), and objects are default initialized, so all fields (even static ones that are assigned in the constructor) can be null.

So, any field de-reference in Java can produce a null pointer exception. Try testing all paths in something like:

try { foo.sideEffect1().sideEffect2()...sideEffectN(); } catch { bar.sideEffect1().sideEffect2()...sideEffectM(); } finally { quuz.sideEffect1().sideEffect2()...sideEffectP(); }

There are M * N * P paths. The only way to write testable code with exceptions is to panic on uncaught exception, and to never catch unexpected exceptions. Even then, you're relying on nothing unexpectedly throwing the exception type you caught.

I'd argue Result<T> >> NULL >> sentries >> exceptions for this reason. Sentries are terrible, but at least they're not computationally hard to use correctly.

Re:Sshole by BoppreH on Thursday November 05, 2026 @11:51AM (#50381403)
I'd argue that's the fault of having default nullability, not the exceptions. You would have the same problem if every attribute access returned a Result<T> too.

And I'm not a fan of breaking control flow, but I still find it preferable to nulls. A null may travel far from the place where it was generated before blowing up, making troubleshooting harder, or it might come from a function you called for its side effect but forgot to check the return value. I gotta say I'm also a careful enthusiast of checked exceptions, it's great knowing which ways a function can fail.

But I think the consensus is growing that Result<T> (plus warnings for unused return values) is simply the superior choice.

Re:Sshole by hedora on Thursday November 05, 2026 @01:21PM (#50382460)
C has what you are asking for, mostly:

   int foo f() __attribute__((warn_unused_result))
As long as an error condition on the return value causes a crash on use, then it's mostly the same as result. (The main remaining difference being that you do an implicit `.unwrap()` in C, and in things like rust that's an explicit code smell.)
Re:Sshole by Sohcahtoa82 on Thursday November 05, 2026 @01:23PM (#50382506)
> There's no way to create a class that prevents callers from reading the object before the constructor runs (in multi-threaded software, new first hands a reference to the caller, which can put it on the heap, then it calls the constructor)

Which is why the pattern of double-checked locking doesn't work.

https://en.wikipedia.org/wiki/Double-checked_locking

Re:Sshole by cowlevel on Thursday November 05, 2026 @10:08PM (#50387676)
It's wrong. The caller doesn't get the reference until the constructor runs. Although it looks like two operations in bytecode, the only thing you can legally do with an uninitialised object reference is call its constructor.
Re:Sshole by hedora on Friday November 06, 2026 @11:45AM (#50394767)
In java, the caller gets the reference before the constructor runs. It's in the language spec. It's terrible. You can prove it yourself with something like:

   this.dangerous = new Lol();
and then have Lol()'s constructor sleep for 10 sec then set done = true at the end of the contstructor. In another thread:

   while(!this.dangerous) { }
   assert(this.done) // will throw.
I guess you could use double-checked locking as a class author by having every public method on your class do a if (!this.done) { throw; }, and make sure there are no public fields.

That's pretty lame though.

Re:Sshole by cowlevel on Friday November 06, 2026 @09:59PM (#50401215)
That's a data race. The caller gets the reference after the constructor runs, but writes to non-final fields from the constructor might not be visible to other threads.
Re:Sshole by hamilyon2 on Thursday November 05, 2026 @01:27PM (#50382562)
> never catch unexpected exceptions

Yes, broad exception catch clauses are simply incorrect code.

Panic is maybe an overreaction, "scratch everything and orderly abort" is sufficient.

Re:Sshole by Ey7NFZ3P0nzAe on Thursday November 05, 2026 @01:32PM (#50382629)
I'm inclined to say a Panic is absolutely preferable to root auth.
Re:Sshole by gf000 on Thursday November 05, 2026 @02:38PM (#50383436)
> There's no way to create a class that prevents callers from reading the object before the constructor runs

That can only happen if you leak the reference (given we are talking about java language level semantics and not byte code), which I give you is unfortunately not that hard to do.

As for exceptions, I think they would pair beautifully with software transactional memory, which would work well with effects. In that case (and no side effects) it would not matter what code path we took, we could just go back to a known safe point.

Because ergonomy-wise I still think exceptions win (even though I do like Maybe types, and Rust's ? macro is plenty good): - they let one focus on the actual happy path code - no error is ever swallowed, unless explicitly written so (looking at you go with useless if errs that fail to properly handle the error), they are always handled somewhere - they automatically "unwrap" the Ok result, and automatically bubble up the Err side. - bubbling up is especially useful because most errors can't be handled at their source (e.g. IO error is meaningless, how you handle it depends on what you want to do in the first place, which is done several layers higher) - they automatically attach a stackframe/source, while with result types you may get a list of Ok Ok Ok Err and have no idea how that Err got there.

Re:Sshole by hedora on Friday November 06, 2026 @11:41AM (#50394735)
As the author of the class, I cannot prevent my caller from leaking the reference, so my public API allows for pre-constructor invocations of all my methods.

Software transactional memory doesn't really work with exceptions, since it precludes the use of catch + finally. It looks more like a top-level panic handler (where the throwing code cannot permute the heap, which is cool). To get exceptions, you'd need nested software transactional memory, which gets into obscure transaction processing theory. There's also the problem that your transaction could side-effect the outside world, then throw and be aborted, so you need to rework how I/O works if you walk down that path.

Exceptions are basically write-only, so I agree that their ergonomics for that use case are pretty good. Panic is better though, since it's really no error handling. Panic is easier to test, and easier to read / understand.

Re:Sshole by quotemstr on Thursday November 05, 2026 @04:52PM (#50384984)
Exceptions work fine and enable a clean separation of business logic and error handling. Visibility into partially-initialized objects is a language design issue independent of exceptions. In a non-exceptional language, the corresponding access to uninitialized fields would just be bugs, not errors you can see.

Nothing in your post actually supports the idea that exceptions are "computationally hard". The CFG error edges exist however the errors are spelled

Re:Sshole by hedora on Friday November 06, 2026 @11:48AM (#50394821)
It is difficult to get to M * N * P control flow edges with O(1) result unwrapping code.
Re:Sshole by quotemstr on Friday November 06, 2026 @12:42PM (#50395615)
You have exactly as many edges if every fallible routine can throw and every infallible one is marked as nothrow.
Re:Sshole by z3t4 on Friday November 06, 2026 @04:23AM (#50390089)
[delayed]
Re:Sshole by za3faran on Friday November 06, 2026 @05:50PM (#50399570)
> There's no way to create a class that prevents callers from reading the object before the constructor runs

Doesn't this address the issue? https://openjdk.org/jeps/539