My intended framing of this does not seem to be coming across in my
arguments. I am not thinking of this as a way to avoid typing ‘try!’ or
‘try?’. This is not intended to replace any of the current uses of
‘throws’. Rather, it is intended to replace trapping and nil-returning
functions where converting it to throw would be burdensome in the most
common use cases, but still desirable in less common use cases. In my
mind, it is only enabling the author to provide extra information and
flexibility, compared to the current behavior.
For example, let’s say I have a failable initializer, which could fail for
2 or 3 different reasons, and that the vast majority of use-cases I only
care whether it succeeded or not (which is why nil-returning was chosen)…
but there may be a rare case or two where I really would prefer to probe
deeper (and changing it to a throwing initializer would inhibit the
majority cases). Then using ’throws?’ allows the primary usage to remain
unchanged, while allowing users to opt-in to throwing behavior when desired.
Right now I end up making multiple functions, which are identical except
for throw vs nil-return, and must now be kept in sync. I’ll admit it isn’t
terribly common, but it has come up enough that I think it would still be
As you say, I think this is a pretty niche use case. When you are in
control of the code, it's trivial to write a second function that wraps the
throwing function, returning an optional value on error. The only thing
you'd need to keep in sync would be the declaration, not the function body,
and that isn't truly onerous on the rare occasion when this is at issue.
The other argument I will make is one of symmetry. We have 3 different
types of error handling in swift: throwing, optional-returns, and trapping.
As the Error Handling Rationale document has pointed out, these three
different types of error handling are meant for different _kinds_ of error.
The idea is that ideally the choice of what kind of error handling to use
shouldn't be down to taste or other arbitrary criteria, but should reflect
whether we're dealing with a recoverable error (throws), simple domain
error (return nil), or logical error (trap). That much can be determined at
the point of declaration. At the use site, there are tools to allow the end
user to handle these errors in a variety of ways, but there is a logic
behind allowing conversions between some and not all combinations:
* A logical error is meant to be unrecoverable and thus cannot be converted
to either nil or throw. To call a function that traps is to assert that the
function's preconditions are met. If it's a possibility that the
preconditions cannot be met, it should be handled before calling the
function. A trap represents a programming mistake that should be fixed by
changing the code so as not to trap. There are adequate solutions to the
few instances where an error that currently traps might not be always have
to be fatal: in the case of array indices, for instance, there's been
proposals to allow more lenient subscripting that don't trap, at the cost
of extra overhead--of course, you can already implement this for yourself
in an extension.
* A simple domain error fails in only one obvious way and doesn't need an
error; the end user can always decide that a failure should be handled by
trapping using `!`--in essence, the user is asserting that the occurrence
of a simple domain error at that use site is a logical error. It shouldn't
be useful to convert nil to an error, because a simple domain error should
be able to fail in only one way; if the function fails in more than one
way, the function should throw, as it's no longer a simple domain error.
* A recoverable error can fail in one or more ways, and how you recover may
depend on how it failed; a user can always decide that they'll always
recover in the same way by using `try?`, or they can assert that it's a
logical error to fail at all using `try!`. The choice is up to the user.
As far as I can tell, `throws?` and `throws!` do not change these choices;
it simply says that a recoverable error should be handled by default as a
simple domain error or a logical error, which in the Swift error handling
model should be up to the author who's using the function and not the
author who's declaring it.
There is already some ability to convert between these:
If you have a throwing function:
‘try?’ allows you to convert to optional-return
‘try!’ allows you to convert to trapping
If you have an optional-return:
‘!’ allows you to convert to trapping
you are unable to convert to throwing (because it requires extra info
which isn’t available)
If you have a trapping function, you are unable to convert to either.
With ‘throws?’ you have an optional return which you can convert to
throwing with ‘try’
With ‘throws!’ you have a trapping function where:
‘try?’ allows you to convert to optional-return
‘try’ allows you to convert to throwing
Thus, ‘throws?’ and ‘throws!’ allow you provide optional-return and
trapping functions where extra information is provided so that it is
possible to convert to throwing when desired. In cases where this
conversion is not appropriate, the author would simply continue to use the
Basically it is useful in designs where optional-return or trapping were
ultimately chosen, but there was also a strong case to be made for making
it a throwing function.
This is totally the opposite use case from that outlined above. Here, you
don't control the code and the original author decided to return an
optional value or to trap. In essence, you're saying that the original
author made a mistake, and what the author considered to be an
unrecoverable error should be recoverable. However, you won't be able to
squeeze useful errors out of it unless you write additional diagnostic
logic yourself. This is already possible to do in an extension, where you
can add a throwing function that checks the arguments before forwarding to
the failable or trapping function. As far as I can tell, `throws!` doesn't
provide you with any more tools to do so.
I think the fears of people using it instead of ‘throws’ are unfounded
because they already have the ability to use optionals or trapping… this
just mitigates some of the losses from those choices.
Does that make more sense?
Maybe I'm misunderstanding something. An author that writes a function that
throws offers the greatest number of choices to their end users for how to
handle errors. You're saying that in designing libraries you choose not to
use `throws` because you don't want to burden your users with `try?` or
`try!`, which as you say allows users to handle these errors in any way
they choose, even though your functions fail in more than one non-trivial
way. This represents a fundamental disagreement with the Swift error
handling rationale, and again the disagreement boils down to: are the four
letters in `try!` a burden? I would just think of it as making every
throwing function at most four letters longer in name.
Put another way, the Swift error handling design says that at the point of
declaration, the choice of `throws` vs. returning nil should be based on
how many ways there are to fail (or more accurately, how many meaningfully
distinct ways there are to recover from failure), not how often the user
cares about that information. If there are two meaningfully distinct ways
to recover from failure in your function, but users will likely choose to
recover from both failures in the same way 99.9% of the time, still choose
`throws`. If there is only one way to recover, choose to return nil. If
there are none, choose to trap.
Put another way, going back to your original statement of motivation:
There are some cases where it would be nice to throw errors, but errors are
rarely expected in most use cases, so the overhead of ‘try’, etc… would
make things unusable.
I disagree with this statement. The overhead of `try` essentially never
tips the balance between unusable and usable, for the same reason that
making a function name three or four letters longer essentially never tips
the balance between usable and unusable.
Thus fatalError or optionals are used instead.
In the Swift error handling model, the frequency with which a user might
have to write `try!` or `try?` should play no role in the author's choice
of throwing vs. returning nil vs. fatalError.
For example, operators like ‘+’ could never throw because adding ’try’
everywhere would make arithmetic unbearable.
As we discussed above, AFAICT, addition traps for performance reasons, as
Swift aspires to be usable for systems programming.
Even if that weren't the case, it would never throw because there's only
one meaningful way in which addition can fail; thus, if anything, it'd be a
failable operation. This would probably not be terrible (other than for
performance), as nil values could be propagated to the end of any
calculation, at which point a user would write `!` or handle the issue in a
more sophisticated way.
(As a digression, for FP values, NaN offers yet another way of signaling an
error, which due to IEEE conformance Swift is obliged to keep distinct;
however, as can be evidenced by the fact that the NaN payload is pretty
much never used, it can be thought of as a counterpart to nil as opposed to
And finally, even if an operator function could fail in multiple ways
(we're really getting to very hypothetical hypotheticals here), writing
`try!` all the time might look silly and non-Swift users might then mock
the language, but I dispute the contention that it would make things
On Sat, Jan 14, 2017 at 8:03 PM, Jonathan Hull <firstname.lastname@example.org> wrote:
On Jan 12, 2017, at 5:34 PM, Greg Parker <email@example.com> wrote:
On Jan 12, 2017, at 4:46 PM, Xiaodi Wu via swift-evolution < > firstname.lastname@example.org> wrote:
On Thu, Jan 12, 2017 at 6:27 PM, Jonathan Hull <email@example.com> wrote:
Also, ‘try’ is still required to explicitly mark a potential error
propagation point, which is what it was designed to do. You don’t have
‘try’ with the variants because it is by default no longer a propagation
point (unless you make it one explicitly with ’try’).
If this is quite safe and more convenient, why then shouldn't it be the
behavior for `throws`? (That is, why not just allow people to call throwing
functions without `try` and crash if the error isn't caught? It'd be a
purely additive proposal that's backwards compatible for all currently
Swift prefers that potential runtime crash points be visible in the code.
You can ignore a thrown error and crash instead, but the code will say
`try!`. You can force-unwrap an Optional and crash if it is nil, but the
code will say `!`.
Allowing `try` to be omitted would obscure those crash points from humans
reading the code. It would no longer be possible to read call sites and be
able to distinguish which ones might crash due to an uncaught error.
(There are exceptions to this rule. Ordinary arithmetic and array access
are checked at runtime, and the default syntax is one that may crash.)
Greg Parker firstname.lastname@example.org Runtime Wrangler