Guard-like syntax for do/catch

I don't like to nest my code whenever possible and I prefer to return early. To accomplish this with try/catch blocks the syntax looks funky and verbose.

Instead of placing all the positive flow code within the do, I will define the variable and bundle up the throwing portion into its own do/catch with an early return on error. Then after the throwing portion I continue with the logic. In complex applications you can imagine multiple of these try/catch blocks keeping the nesting to 1 level, instead of increasing the level each time.

func throwingFunc() throws -> Int {
    return 0
}

func example() {
    let value: Int
    do {
        value = try throwingFunc()
    } catch {
        print("Do something with \(error)")
        return
    }

    doSomething(with: value)
    ...
}

I would like new Swift syntax to be able to accomplish this more succinctly.

Some possible solutions:

// do without defining a block
do let value = try nilThrowingFunc() catch {
    print("Do something with \(error)")
    return
}

// guard acting like do, enforcing return on catch and unwrap
guard let value = try throwingFunc() else {
    print("value is nil")
    return
} catch {
    print("Do something with \(error)")
    return
}

// do returns inner value like `DispatchQueue.sync()`
let value = do {
    return try throwingFunc()
} catch {
   print("Do something with \(error)")
}
...

The main purpose really is to define value in the same scope as where I'm calling try. Is there any current discussions on this? What does everyone think?

13 Likes

I would love this syntax — the ceremony around do/catch right now makes it frustrating to handle errors from multiple calls to throwing functions. I believe this pitch/proposal is the deepest exploration of the issue.

9 Likes

I think the syntax is very confusing. Either the keyword is always followed by a closure, or it is always followed by a block, but we really should not mix them.

do {
    return try throwingFunc() // return applies to outer scope.
} catch { … }

let value = do {
    return try throwingFunc() // return applies to inner scope.
} catch { … }

guard / catch will be even more useful with the Swift Concurrency work, given the prevalence of try await in most of the examples (and my own test usage).

2 Likes
Terms of Service

Privacy Policy

Cookie Policy