Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I'm not trying to be provocative, but I genuinely am not seeing what the benefit of this "doctrine" is if the end result is a bunch of if else statements that do nothing, but bubble the error up and then exit the process anyway.

Panicking is in no way different from say Undefined Behavior, with the exception that panicking tends to be "loud" and therefore fixed promptly.



There are systems banning exceptions that aren't allowed to crash. The software running cars and planes for example, or LLVM.


EDIT: HN formatting is absolute horrendous so here's a pastebin https://pastebin.com/raw/BUJBAqc1

> but bubble the error up and then exit the process anyway.

What is so bad about this? Its only a problem because all languages are not ergonomic. Even Rust. Here's how to do it properly in my mind.

What I am proposing is not Rust, but what I wish Rust would have been.

First, you have functions that cannot fail

``` fn not_failable() -> Result<u32, ()> { return 0; } ```

Notice that the function still returns Result<T,E> but the error is (). Now what happens if the function can fail?

``` fn failable(value) -> Result<u32, () | A | B | C | D> { if value == 0 { return Ok(0); } else if value == 1 { return Err(A()); }else if value == 2 { return Err(B()); }else if value == 3 { return Err(C()); }else if value == 4 { return Err(D()); } } ```

Notice that we don't have to specify a type for the errors, they are just the unions of all the error types that is possibly returned by the function. This union could be inferred by the type system to be ergonomic (meaning it can be omitted from the type signature for ergonomic purposes)

You might think that this is almost like exceptions. And you are right, but this is where exceptions got wrong, the user of this function.

When using this function, you are forced to handle all the possible error types (exceptions) returned by the function

``` fn use1() -> Result<u32, () | C | D> { match failable() { Ok(v) => {} Err(A) => {} Err(B) => {} e => return Err(e), } } ```

Notice 2 things:

1. You are FORCED to handle all the possible exceptions. 2. You can specify which exceptions you want to handle and what you throw back. The difference to try/catch here is just the syntax. 3. The function signature can be automatically be duduced by the fact that A and B are already handlded, and that this function can only throw C or D now.

Now you might complain about ergonomic, why can't things just blow up the moment anything bad happens? I propose a trait that will be automatically be implemented for all Result<T,E>

``` impl Unwrap for Result { fn unwrap(self) -> u32 { match self() { Ok(ok) => return ok, Err(err) => panic!("error"), } } } ```

Which means that you can simply do this,

``` fn fail_immediately() -> Result<u32, ()> { return failable().unwrap(); } ```

Or, if you want to bubble up the errors, you can use ?

``` fn fail_immediately() -> Result<u32, A | B | C | D> { return failable()?; } ```


That looks just like zig error handling to me. The only missing component being that errors themselves are just a tag without any more data content.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: