Hacker News new | ask | show | jobs
by mrec 3350 days ago
As I see it, the Rust (etc) approach avoids two problems:

1. The C problem of forgetting to check error returns. Yes, exceptions (checked or not) also avoid this.

2. The C++/Java/C# problem of exceptions being more expensive than you'd like for common error situations. .NET has sprouted alternatives like `bool tryParse(String, out int)` as a workaround, but on balance I prefer the unified mechanism.

What I don't like is how innocuous `unwrap` looks, or how often it appears in example code.

2 comments

> The C++/Java/C# problem of exceptions being more expensive than you'd like for common error situations.

Exceptions should not be used for common error situations! This is the prime mistake of Java which pretty much required exceptions when it should (and checked exceptions to boot).

> .NET has sprouted alternatives like `bool tryParse(String, out int)` as a workaround

I don't consider that a workaround. There is a deep semantic difference between TryParse and Parse. If you see TryParse then you know the data is expected to be invalid. If you see Parse than you know it's expected to be always valid. A good C# program should have very very few try/catch blocks (ideally just one).

> There is a deep semantic difference between TryParse and Parse

Sure, but I don't think that splitting every possibly-failing API call into throwing and non-throwing forms is the right way to express that difference, and a lot of the time it'll be a matter of context (meaning that the implementation can't magically do the right thing).

It's fairly easy to layer throwing behaviour on top of nonthrowing in a generic and efficient way (Rust's Option, Java's Optional etc), but the reverse is not true.

I must admit I'm losing track of what if anything we're in disagreement about, though...

I agree it's easy to layer throwing behavior on top of non-throwing behavior -- Java easily chose the worst possible way to do it.

But having both a Parse and TryParse means that I can ignore the result of the Parse call entirely and let it fall through to the exception handler. It is by-definition always expected to succeed so when it doesn't then that's a bug. If you only have one of TryParse or Parse you cannot judge the intention.

> If you only have one of TryParse or Parse you cannot judge the intention.

Sure you can. To take Java as an example, if you only have one parse method and it returns an `Optional`, you can indicate the intention by whether or not you call `get` directly or call something like `isPresent` or `orElse` first/instead. Yes, you can get that wrong, but you can get the choice between `TryParse` and `Parse` just as wrong.

That's a very good point. I do this all the time with nullable types; I'm not sure why I didn't consider it that way.
It's not really that innocuous. It'll cause the program to crash as soon as it's discovered that there wasn't a value where you were expecting one.

In languages with exceptions, the program will crash as soon as you try to use that value (rather than when you try to unwrap it), e.g. the infamous null pointer exception. Copying bad sample code in this case might result in code that is difficult to debug because a null value might be handed off several times before something tries to dereference it.

In languages that expect but do not enforce that you check the validity of the value (like C) you'll just get undefined behaviour that will hopefully cause your program to segfault when you try to use the value, but who knows what will actually happen? Copying bad sample code in this case will cause a security vulnerability.

Copying "bad" sample rust code (using unwrap) will cause a safe crash with maximum locality, for simpler debugging.