Hacker News new | ask | show | jobs
by jagadaga 4 days ago
>I've long faulted Rust's use of Result over exceptions

Why?

2 comments

Not the person you're asking, but result types pessimize code more, add register pressure, use more icache for largely dead code, etc. They're arguably noisier at the source level too.

That said, I've spent too much of my life chasing implicit control flow to accept exceptions. Heck, C++'s "noexcept" is a strong argument against exceptions all by itself.

> That said, I've spent too much of my life chasing implicit control flow to accept exceptions.

Yes. This is why unchecked exceptions are annoying. Checked exceptions are a „failed experiment“ anyway.

A result type (or even Go‘s err) with forced error handling meaningfully increases the robustness of a program in my experience.

My view is that Java's checked exceptions failed in big part because Java didn't have type variables back then; after all, polymorphic Result types are not that much different from checked exceptions.

I haven't written any Java since it has gained those, so I suspect there might be other aspects making it awkward. And it still would lack exhaustiveness checking that e.g. Rust has.

Checked exceptions were actually the "right way" to do exceptions (if there is such a thing), the problem was just that developers hated them, but I think that draws the wrong conclusion. That tells me their syntax/usage was seen as too much forced boiler plate making code unwieldy, not that they didn't have benefits for correctness (something often not appreciated until years later).

Unchecked exceptions lead to unhandled exceptions at runtime. We've all seen screens with Java programs running with tons of exceptions in the log, or worse, that crash with unhandled exceptions. This is the result of the unchecked exceptions mess, which is why I won't use languages that use them for routine error handling for anything more than trivial programs.

> developers hated them, but I think that draws the wrong conclusion

Exception handling is just annoying from a syntax perspective. Also checked Exceptions have the problem that they bubble up types that a different layer shouldn’t even be aware of due to exception chaining (cause of a cause etc), unless you carefully re-throw them, which nobody did.

Lower ceremony errors are just better to deal with.

Agreed, I really like Rust's Result type. Go's idea is okay, but they should force you to handle the error.

    > result types pessimize code more
What does it mean?

    > They're arguably noisier at the source level too.
But they are more explicit, which means there will be fewer bugs in your code.
C++ noexcept exists because the way C++ evolved, it is no argument against exceptions.

Exceptions came to be during the 1990's, as the C++ARM to C++98 standardisation process was taking place.

Naturally during the early days C++ compilers lacked exceptions, as CFront was introduced without them.

Then we had the C folks that were migrating to C++.

When C++ compilers finally supported exception, compilers vendors introduced the non standard switch to disable them, so that existing code could still compile with the expected behaviour.

The standard itself does not acknowledge this as allowed.

This feature was naturally misused by the anti-exception folks, same applies to how RTTI came to be.

So noexcept is a way to be able to write code to appease both camps, while surfacing what variant was chosen by the programmer.

From my point of view allowing disabling exceptions in first place was a mistake, those folks should have moved back to C, or fixed their code.

I don't understand why Result type is necessarily slower. Result that simply bubbles up is basically an exception, no?
You need instructions to check the result type, and a bit somewhere to store whether it's valid. Most exceptions only add extra instructions during throws/unwinding. There may or may not be an overall reduction in code size, depending on how many checks you have vs table entries.
In theory, a compiler can figure out that you don't need that in some cases.
The worst part about rust not having exceptions is that it actually does have exceptions, you just shouldn't use them. All the downsides and none of the benefits:

https://smallcultfollowing.com/babysteps/blog/2024/05/02/unw...

Just like on Go's case, panic/recover are exceptions with bad ergonomics.
This is a vacuous statement on par with characterizing setjmp/longjmp in C as "exceptions with bad ergonomics".
Which they kind of are, and were used as implementation mechanism for C++ exceptions in the early days on UNIX compilers.
No, Rust does not have exceptions. Rust has unwinding, if you choose to compile your program with support for it, which you are free not to, and is trivially achieved by a single flag.
It's hard for me to think of a definition of "exception" which doesn't include Rust's panics:

  use std::panic::catch_unwind;
  fn throws_exception(){
      panic!("hello");
  }
  fn main(){
      match catch_unwind(|| {
          throws_exception();
          println!("Never reached");
      }) {
          Ok(_) => (),
          Err(e) => println!(
              "threw an exception: {:?}",
              e.downcast_ref::<&'static str>().unwrap())
      }
  }
I can disable exceptions in rustc, but I can't disable the influence they have on language and library design (as detailed in the link I posted). Exceptions are the main reason you can't temporarily move something out from behind an exclusive reference, or return an error code from a Drop impl. Heck, Rust wouldn't even need destructors if it weren't for exceptions: the compiler could just tell you when you forgot to free something.
No, panics are not exceptions. Exceptions are a mechanism for resumable error-handling. Rust's `catch_unwind` function is a last-ditch mechanism for failure isolation (motivated by preventing UB via unwinding across FFI boundaries), not a general-purpose error recovery mechanism. I have seen many Rust programs in my day, and not a single one has ever used `catch_unwind` in the way that (say) Java or Python programmers use try/catch for error handling, so this is as true in practice as it is in theory.

> I can disable exceptions in rustc, but I can't disable the influence they have on language and library design

It's the other way around. The fact that unwinding can be (and regularly is) trivially disabled is the ultimate motivator of why panics fundamentally cannot be used for error handling, and this is what influences language and library design (ultimately demanding `Result`-based error handling).

> Exceptions are the main reason you can't temporarily move something out from behind an exclusive reference, or return an error code from a Drop impl

No, this is absolutely untrue, and I'm not sure why you think this.

> Heck, Rust wouldn't even need destructors if it weren't for exceptions: the compiler could just tell you when you forgot to free something.

No, this is also untrue, and you appear to misunderstand the purpose of destructors. Feel free to provide code if you would like to try to make a more precise argument against unwinding.