[swift-evolution] [Pitch] Guard/Catch

Christopher Kornher ckornher at me.com
Sat Jul 8 15:08:07 CDT 2017


I am opposed to this proposal because it muddies up the language to support what is essentially an edge case. The standard way to exit a function early because an exception is thrown is to make the function itself throw, if it is part of a larger operation. The addition of a few lines of try/catch code is not a great burden and makes the termination of an an exception very clear.

`guard` statements are generally used to set variables that are needed in the body of a function. Using them to save a few lines of exception handing code is a very different use. There is no need to mix two relatively clean syntaxes for a few edge cases and increase cognitive load one more time, 

If we really want a more compact try/catch syntax, we should not use (abuse?) the `guard` statement, but instead allow a training catch which reads similarly to a trailing closure:

```
let x = try aFunction() catch ( let e: Error ) {
 	… 
} 

```

This might be confused with trailing closures, but since most editors will highlight the `catch`, the meaning should be pretty clear.


Using this in a `guard` would look like: 

```
guard let x = try aFunction() catch ( let e: Error ) { … // must exit };
	let y=y;
	let z=x else {
	...
}
```
Multiple throwing statements in the guard could be handled like:

```
guard let x = try aFunction();
	let xx = try aFunction2() catch ( let e: Error ) { … // must exit };
	let y=y;
	let z=x else {
	...
}
```
but this creeps toward the original proposal and I do not think that the added complexity is worth the trouble. I prefer the simpler syntax for the rare cases where is more than one throwing statement within the guard:

```
guard let x = try aFunction() catch ( let e: Error ) { … // must exit };
	let xx = try aFunction2() catch ( let e: Error ) { … // must exit };
	let y=y;
	let z=x else {
	...
}
```

This addition, I believe, adds much more power to the language and retains the simplicity of the “guard” statement.


It helps clean-up code like:

```
func attemptComplexOperation() throws {
	do {
		let x = try functionA()
	} catch ( let e: Error ) {
	Log.error( “could not do A: \(String(describing:e)” )
throw e
}

	do {
		let y = try functionB()
	} catch ( let e: Error ) {
	Log.error( “could not do B: \(String(describing:e)” )
throw e
}
}
```

to:

```
func attemptComplexOperation() throws {
let x = try functionA() catch ( let e: Error ) {
	Log.error( “could not do A: \(String(describing:e)” )
	throw e
}

let Y = try functionB() catch ( let e: Error ) {
	Log.error( “could not do B: \(String(describing:e)” )
	throw e
}
}
```

This this is error-prone because exception rethrowing could be omitted and the code would still compile. Perhaps another keyword to act upon an exception, but not consume it would be useful (I am sure that this idea will be popular :) )

```
func attemptComplexOperation() throws {
let x = try functionA() intercept ( let e: Error ) {
	Log.error( “could not do A: \(String(describing:e)” )
}

let Y = try functionB() intercept ( let e: Error ) {
	Log.error( “could not do B: \(String(describing:e)” )
}
}
```

I hope that the themes for Swift 5 comes soon from the core team, so we can slow down the discussions of the 'syntactic sugar proposal of the week'. I fear that Swift will become a much more complex and difficult to read language if we keep adding sugar to handle special cases. I hope that it doesn’t become too difficult to understand through all the special-case sugar coating. Perhaps more importantly, there are important features awaiting implementation that are needed by members of the community that will be delayed and possibly complicated by the addition of syntactic sugar.

If the decision is to accept the original proposal, then I vote for #1 below. Note that exceptions could be handled within the else clause by changing the order. I don’t think that this would be a good idea, however, as it makes the statement even harder to understand:


Please, not this:
```
guard let result:Result = try doSomething()
else {  try cleanup() }
catch { ...}
```


> On Jul 8, 2017, at 12:15 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.
> 
> 
>  - Ben Spratling 
> _______________________________________________
> 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/20170708/915c09ff/attachment.html>


More information about the swift-evolution mailing list