[swift-evolution] [Pitch] Guard/Catch

Xiaodi Wu xiaodi.wu at gmail.com
Sat Jul 8 15:02:48 CDT 2017


On Sat, Jul 8, 2017 at 2:11 PM, Benjamin Spratling via swift-evolution <
swift-evolution at swift.org> wrote:

>
> func doSomething() throws → Result? { … }
>
> How would you handle both the catch and the let binding of the result?
>
>
> I agree this proposal needs to define the behavior, since returning an
> optional from a throwing function is valid.
>
> I see a few options:
>
> 1) find a way to generally incorporate optional binding and error binding
> into the same guard clause.
> Could it be as simple as
> guard let result:Result = try doSomething()
> catch { ...}
> else { ... }
> ?
>
>  In this option, the else block almost acts like "finally" block.  I
> recommend against this option because IF the developer has defined a
> function which returns nil and doesn't throw an error, they clearly intend
> for the nil to be a valid representation of state, and thus one the
> developer may wish to examine in more detail and potentially continue the
> function. I. E. Let's give the developer a chance to work with the nil
> value and not return.
>
> 2) Ignore it completely.  I.e. define the guard/catch pattern as not
> compatible with returning Optionals.  Throwing an error is usually
> considered an alternative to returning an optional, and importing Obj-C
> won't import throwing methods in this way. So I don't think prohibiting it
> is a totally absurd idea.  This makes it impossible for the developer to
> code and build code which accidentally does something he doesn't expect.
>
> 3) bind the optional value
>
> Perhaps, when guard/catch-ing, an optional value is perfectly legal.
>
> guard let result:Result? = try doSomething() catch ...
> This is essentially how guard works with a try?today.
> guard let result:Result? = try? doSomething() ...
>
> In case 3, I think we have a potentially ambiguous behavior, because with
> type inference the programmer may expect that the value has also been
> optionally unwrapped.  Of course, he'll catch that later on in the
> function, but my concern is for the poor unwitting developer (such as
> myself) who is frustrated that his code doesn't compile and he doesn't
> understand why.  "But I did a guard let!   Why is it optional?!"  One
> option is to require the type be typed explicitly when guard/catching an
> optional, but I always type my types explicitly anyway, so I get that
> others may be more resistant to that idea.  Another option is to allow it,
> but just issue a warning until the developer adds the explicit type, such
> as requiring "self." in an escaping closure.  (Of course the compler can
> figure it out, this syntax is required so the developer can figure it out!)
>  Another option is for the smarts to be built in the the error/warning
> system to point out when an expression gets used as a non-optional when it
> came from a guard/catch to point it out, much as it points out the first
> instance of a function which has been defined twice.
>

Agree that (3) is potentially ambiguous and has high likelihood to be a
footgun.

Also agree that _if_ the developer defines a function that can return nil
*and* can throw an error, nil is likely to be a bona fide representation of
state. One possibility--with an obvious problem (see below)--is therefore
to have the successful result of guard...catch be of type `Result?`:

```
guard let result = try doSomething() catch { ... }
guard let unwrappedResult = result else { ... }
```

The obvious problem is that `guard let` in Swift is closely associated with
optional unwrapping. The reader is right to expect a non-optional `result`
with `guard let` regardless of the word that comes after conditional
expression. There are two ways to see this:

One way is: If we consider `doSomething() throws -> Result?` to be a
function where throwing indicates that the operation has failed and nil is
a bona fide representation of state, then it can be seen as a function more
similar to `doSomethingElse() -> Result??`, where the outer Optional
indicates whether the operation succeeded (see Swift error handling
rationale document) and the inner Optional is a bona fide representation of
state. Therefore, just as `guard let result = doSomethingElse() else { ...
}` gives you a result of type `Result?`, so should `guard let result = try
doSomething() catch { ... }`.

However, I can see how this might be confusing to users. The fundamental
problem here is one of spelling, where users instinctively see `guard let`
and expect one level of optional unwrapping. This idea is reinforced
because `guard...else` does *not* permit `guard let` when the result isn't
optional. Therefore, another way to solve this issue is to make this rule
also apply to `guard...catch`. That is, make the syntax of `guard...catch`
_not_ use `guard let` unless the result is optional:

```
let result: Result?
guard result = try doSomething() catch { ... }
```

However, though it does solve the stated motivation of removing one level
of braces, this form of `guard...else` is hardly an improvement over the
status quo:

```
let result: Result?
do { result = try doSomething() } catch { ... }
```
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170708/b7bed3d3/attachment.html>


More information about the swift-evolution mailing list