[swift-evolution] Idea: Extend "guard" to try-statements, with a catch block

Adrian Zubarev adrian.zubarev at devandartist.com
Sun Mar 13 08:17:21 CDT 2016


This is the exact same topic I started over a moth ago: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160201/009296.html

As already discussed in my topic it’s better not to use `guard` in this case, but add a new single statement `try catch` mechanism.

func scope() {		
	let foo: Int? = try throwingFuncReturns() catch { 
		// foo can’t be available here, because its
		// declared on the same line  
		// by default you can fall through the catch block
	}
	// using foo would not be save here
	guard let unwrappedFoo = foo else { 
		return
	}
	use(unwrappedFoo)
}
On way to solve this problem for throwing functions that return a value could look like this:
func scope() {		
	let foo: Int? 
	foo = try throwingFuncReturns() catch { 
		// foo is available here
		// make sure to initialize foo here if you do not return from here
	}
	// foo is save here
	guard let unwrappedFoo = foo else { 
		return
	}
	use(unwrappedFoo)
}
And because we just can’t use a single `try catch` statement like we would wish, we can bring back the `do` keyword.
func scope() {		
	let foo: Int? 
	do foo = try throwingFuncReturns() catch { 
		// init foo (+ more optional work) or return
	}
	guard let unwrappedFoo = foo else { 
		return
	}
	use(unwrappedFoo)
}
This is the best solution I could come up with, and it does work fine with functions that do not return a value:
func scope() {		
	let foo: Int? 
	do try catchMeIfYouCan() catch { 
		// do some work -> fallthough or return
	}
}
But from my personal point of view I would remove the `do` keyword in single `try catch` statements. The `do` keyboard is only needed for the `do body` if there are more lines of code to process before catching an error.
A single `do catch` mechanism would be good for escaping another pyramid of doom like we had with `if` before the `guard` mechanism was introduced.
-- 
Adrian Zubarev

Am 8. März 2016 bei 20:37:52, Jacob Bandes-Storch via swift-evolution (swift-evolution at swift.org) schrieb:

Thanks for the feedback, Chris.

To clarify, are you suggesting that plain variable bindings would support this too (such as "let x = try foo() catch { ... }")? I like this idea. I can also foresee the desire for something like this:

    let x = try foo() catch {
        print(error)
        x = bar()   // by some syntax, provide a fallback value for x, since foo() threw
        // control flow *can* escape, since x has a value
    }

Of course, you can achieve this today with "let x; do { x = try foo() } catch { x = bar() }", or with "let x = try? foo() ?? bar()". I just wonder if it's worth considering the possibility that this new feature would allow control flow to escape in some cases. (After all, control flow can exit the "catch" block we have today.) But it's also nice to be able to glance at the code and see that, unambiguously, control flow can't escape the block.

The reason I originally suggested "guard" is readability: Seeing the word "guard", a reader knows that control flow can't escape the else/catch block. But I understand why guard is still necessary for working with optionals/patterns, and I suppose seeing "try" introducing the expression may be enough.

Do you have thoughts on whether else/catch blocks should be re-orderable?

Another question: should it work with expressions that don't bind variables? Simply "try foo() catch { ... }" ?  (At one point I had considered "do try ... catch ...", as a braceless analogue of "do { try ... } catch ...".)

Jacob

On Mon, Feb 29, 2016 at 10:34 PM, Chris Lattner <clattner at apple.com> wrote:
On Feb 29, 2016, at 12:09 PM, Jacob Bandes-Storch via swift-evolution <swift-evolution at swift.org> wrote:
I propose extending guard-statements to handle errors without using the optional "try?" and without a do-block, by allowing the expression to throw, and offering "catch" instead of "else":

    // func foo() throws -> T ...
    guard let x = try foo catch {
        print("the error was: \(error)")  // the "error" parameter is available here
        // the compiler does not allow control flow to escape this block
    }
    // control flow cannot reach here if foo() threw an error

I don’t think that this syntax makes sense, because you’re changing "guard let x = ” to not test an optional.  The syntax you’re searching for seems more consistent as a modifier on var/let itself:

// func foo() throws -> T ...
let x = try foo() catch {
        print("the error was: \(error)")  // the "error" parameter is available here
        // the compiler does not allow control flow to escape this block
}

The guard form of this would still make sense, but the existing “guard let” and “guard case” matching should work as it does.  For example, something like this should be allowed:

// func bar() throws -> T?
guard let x = try bar() else {
        // this runs if ‘bar’ returns nil.
        // the compiler does not allow control flow to escape this block
} catch {
        print("the error was: \(error)")  // the "error" parameter is available here
        // the compiler does not allow control flow to escape this block
}

More generally, the “guard” form would be workable on anything that takes a stmt-condition.  This brings “if" and “while” into the mix.

-Chris

_______________________________________________
swift-evolution mailing list
swift-evolution at swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160313/cdb51f76/attachment.html>


More information about the swift-evolution mailing list