Hacker News new | ask | show | jobs
by krisdol 3891 days ago
>Gonna have your function return an Err if an internal invariant is broken?

Absolutely you should return an error. Whether the caller wants to panic or handle it or print unicorns should be left up to the caller, not your function. Functions should not be expected to tear down the thread in case of an error. Nothing that panics should belong anywhere in exported code

2 comments

This seems a bit too unpragmatic. Would you also require the user to explicitly handle:

* Index out of bounds on every array op

* Integer overflow on every arithmetic op

* OOM on every allocating op

Maybe if you're writing an ironclad RTOS for a critical system without any good redundancy? Otherwise these are such pervasive operations that most have accepted that they're not worth handling everywhere they happen. Requiring that really dilutes the value/meaning of errors.

Given this signature:

fn do_thing() -> Result<T, E>

There's a clear signal that there's legitimate error conditions that you probably want to think about today. If every function that accessed arrays, worked with integers, or allocated memory returned a Result, it would border on meaningless. It would be like if there was no Throwable/Exception distinction in Java. Everything would `throw Exception`, eliminating the value of even noting that something can fail.

I think the only way such a system could be tolerable is if the language in question had really good dependent type support (but that wouldn't handle the OOM issue -- which you can't even reliably handle on some systems).

Unwinding/crashing is valuable in a truly robust system, because it needs to handle crashes anyway. Might as well punt obscure problems to that level of the reliability system. (This is basically the basis of Erlang's task system, AFAIK)

>Requiring that really dilutes the value/meaning of errors.

No. You have already diluted the meaning of errors, and you want them elevated to _your_ standard.

>Index out of bounds on every array op

These are removed if you build a rust program with --release.

>Integer overflow on every arithmetic op

Add the Wrapping class if you expect overflow. Overflow _shouldnt_ normally happen on an Integer operation. It is a hardware error when it happens, and can cause massive pain-in-ass bugs when it happens unexpectedly.

I'd rather get errors when it does happen, rather then find out 6 months into a production run.

>OOM on every allocating op

C does this also.

What have I diluted the meaning of errors to?

Rust doesn't remove bounds checks in --release. It's wrapping that gets turned on in release. I'm not sure why you're distinguishing overflow as truly exceptional, as opposed to any other "this should never happen" error?

Also I don't think many C libraries that allocate expose that as a failure condition (I've certainly seen some which don't even check!)

>Also I don't think many C libraries that allocate expose that as a failure condition (I've certainly seen some which don't even check!)

This is a very common mistake in most C code. Malloc can fail, and it return NULL when it does.

IMO the more important example that Gankro failed to mention is division, where your language has to do something when the denominator is zero.
The idea is that Err is good if it's a recoverable error, but if it's not recoverable, you should panic!.

Most errors are recoverable. panic! is an antipattern in a library. Or at least, provide both a panic-ing and a non-panic-ing variant.

We specifically recommend against providing panicing and non-panicking. This causes horrible combinatoric explosions. There's very few exceptions to this (array indexing being the major exception). We do of course recommend non-panicking, but there's a few cases where the burden of checking for errors is too high (indexing, refcell).

We're actually in the midst of arguing whether to violate this convention and do it for RefCell: https://github.com/rust-lang/rust/issues/27733

Yeah, I was thinking of things like array access. The stuff that's on the wrong end of the cost/benefit ratio.
The problem is that a function cannot know whether or not the caller can recover from a particular error, so there's no point in making that distinction in the first place.
It's part of your API design, if they are recoverable or not. You should lean towards recoverable: if the caller can't recover, they can always do something with it. But some kinds of problems are non-recoverable.
Can you name one such unconditionally unrecoverable problem?
Say you have a library which has some kind of internal state, and the consistency of said internal state is important for memory safety, as other operations rely on it.

Using assert! to verify that your state is consistent is useful, in case you have a bug. But since it's all internal, it's not something that the caller can really recover from, either. It's not their fault, it's yours.

Well here's my issue with this explanation that I've heard a lot: the caller may or may not be able to recover from it, but the caller may still be doing something with the error. Maybe the caller doesn't want to expose the error to its caller, or maybe the caller wants to build a new message to explain the cause of your error. But to panic! takes away a lot of options for the caller.

Worst of all, it leads to untestable code, because the calling test case can't properly check the error against some known result.