That's not what I'm saying: I'm saying that unreachable code is only one use case for fatalError.

These are dime a dozen. For example, fixed-width integer arithmetic using the standard +, -, and * operators must trap on overflow: those are the documented semantics of those operators. If you are implementing a custom type that conforms to FixedWidthInteger, you are basically required to use fatalError (unless you contrive to spell fatalError some other silly way such as computing Int.max + Int.max), and you sure as heck need to test that the behavior is correct with inputs that should cause overflow.

1 Like

Okay, that makes sense. I'd usually consider triggering an integer overflow also to be a programmer error, but from the POV of someone implementing the type I can see why this would be important.

How about, then, we alter the pitch to apply exclusively to preconditionFailure() and friends? The documentation for preconditionFailure() states:

Use this function to stop the program when control flow can only reach the call if your API was improperly used and execution flow is not expected to reach the call—for example, in the default case of a switch where you have knowledge that one of the other cases must be satisfied.

which seems fairly clear.

edit: I've edited the OP accordingly.

I don't see the difference that documentation for preconditionFailure() makes vs using fatalError() to check for programmer error. If I'm implementing a library I might use either preconditionFailure() or fatalError() to catch programmer error; in both cases I'd ideally like to be able to test that the API correctly traps when mishandled.

Why would that be? Does it really make sense to have two functions that do exactly the same thing? Shouldn't one of these be preferred over the other for a given purpose?

Another excerpt from the documentation for preconditionFailure that, IMO, makes it pretty clear that it's meant to be put in places where you don't expect the code execution to go:

  • In -Ounchecked builds, the optimizer may assume that this function is never called. Failure to satisfy that assumption is a serious programming error.

-Ounchecked is specifically designed to bypass safety checks that normally protect an API in order to improve performance. If you compile your library without -Ounchecked (which is the typical case) then you expect predconditionFailure() to be invoked when your clients misuse your APIs, and it's useful to be able to test that the predicate for those checks are correct.

Edit:
You could certainly argue that preconditionFailure() should be synonymous with "unreachable" when -Ounchecked is specified, but I don't think it makes sense to do so always.

They don’t do exactly the same thing.

It is a precondition failure to subscript an array (or other collection) with an out-of-bounds index, and it is a serious user error to use that API with such an index. The implementor of the collection type must nonetheless make sure to test that this precondition is checked and fails as expected.

It is not a precondition failure to overflow during multiplication. It would be unreasonable to tell users that they’re wrong to try to multiply without checking for overflow, since in the general case the only way to do this would be to…do a full-width multiplication. But, it is a fatal error to overflow. The implementor of the multiplication operation has to test for that behavior just the same.

1 Like