[Pitch] Guard/Catch


#1

One of the reasons I like this proposal is at a higher level we are
checking for errors and have the ability to exit early if there is an
error, I think this aligns well with what `guard` represents, at least to
me. Using `try?` effectively ignores the error so if we want convenience we
have to give up accuracy of the error handling.

It also encourages error handling, if we have a function that uses the
result of a throwing function it's very easy to do a `guard` on `try?` and
return a generic error instead of putting the function in a do/catch block.
With this proposal we can easily put the specific error handing in the
catch block.

The proposal does also changes the concept of guard somewhat, where now it
is tied directly to conditionals and `guard/else` is the core concept. The
proposal takes out `guard` as the core of and early exit statement the
result of it failing it up to how we define our handing of it.

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.

Yes this does change that idea, but `guard let` is always accompanied by
else, additionally because we also use guard along with other conditionals
`guard x > 0 else {}` there isn't a strong tie of guard to optionals. The
interfered result type is set by the entire line much like `let a = x > 0 ?
"higher" : "lower"`

The nesting and ceremony, to me, were part of Swift’s philosophy of

making error handling explicit. Merging catch blocks into guards saves you
maybe 3-10 lines if you intended to actually handle the error(s), otherwise
this effectively try?’s into a failable pattern match. At which point,
you have to wonder if the error-throwing function you wrote wouldn’t be
better off just returning an Optional if you’re going to discard the
semantic content of the error.

I think this proposal goes beyond convenience as we are stating that we
want the returned value to be at the same scope as code that uses it.

If we want to save lines we can do that anyway with something like:

func divide(x: Int, y: Int) throws -> Int {...}
let z: Int; do { z = try divide(x: 10, y: 5) } catch {
    print(error)
    return
}

But we always want `z` to be the result of `divide(x:y:)` having it outside
the do/catch looks like we may intend it to be set by something else. With
this proposal we are tying `z` to the result of `divide(x:y:)`.

Another way could be to use a closure

let z:Int? = {
    do {
        return try divide(x: x, y: y)
    } catch {
        print(error)
        return nil
    }
}()

but then we have to use an optional in the case where `divide(x:y:)` does
not return one and adding another check for the optional later.

I think separating guard/else and guard/catch is a great idea because we
wouldn't have the issue of remembering what order else and catch should be
in. We can think about guard as guard/[handler]

The prior thread and in this thread there as been talk of a version without
the `guard`. This feels more like an extension of the try keyword, which
doesn't sound to bad to me. It handles the case of keeping the result in
the current scope, but it just doesn't group code in a exit early style.