[swift-evolution] [Proposal]: Escaping another (unused) scope pyramide with 'guard try catch'

Félix Cloutier felixcca at yahoo.ca
Fri Feb 5 16:13:14 CST 2016


The privileged `guard` syntax is convenient because optionals can propagate to variables and the most convenient way to safely unwrap an Optional is to use pattern matching. However, AFAIK, you can't save the wrapped result of a throwing function into a variable: you need to unwrap it as the call is made. This means that you can always write:

let foo: Int
do foo = try funcThatThrows() catch { /* snip */ }

You can fall through, but if you do, `foo` will be in a quantum superposition state of "initialized" and "not initialized", meaning that you won't be able to access it or initialize it, so if fallthrough wasn't intentional, you should find out pretty quickly.

I do agree that it would be a little prettier if the declaration was on the same line as the assignment.

Félix

> Le 5 févr. 2016 à 14:18:25, Adrian Zubarev via swift-evolution <swift-evolution at swift.org> a écrit :
> 
> +1 You are right Félix, it was only my own project that tempted me to return from each catch scope. 
> 
> But wouldn’t this confuse everyone?
> 
> Here is an example:
> 
> do let/var newInstance = try throwingFuncReturns() catch {
> 	// can not fall through at all
> }
> 
> There is no chance the compile can guarantee that `newInstance` was assigned with a value, except if `newInstance` is an optional type, so this code may not fall through like the 'guard‘ mechanism.
> 
> let newInstance: Type
> do newInstance = try throwingFuncReturns() catch {
> 	// if not returned from here or the newInstance is not set here the compiler should raise an error
> }
> 
> `newInstance` is only safe when throwing function returns without any errors or the instance was set inside the catch scope. This mimics the `if else` behavior. This can already be done with the existing `do try catch` mechanism.
> 
> let x: SomeThing
> if condition {
> 	x = foo()
> } else {
> 	x = bar()
> }
> use(x)
> 
> do try throwingFunc() catch {
> 	// can fall through if needed (this means I can ignore the error or return/break)
> }
> 
> Another way do this right now would look like this:
> 
> do {
> 	try throwingFunc() 
> } catch _ { } // will ignore the error
> 
> As you can see only the first example can not fall though after catching the error, so wouldn’t it be better to support both syntax variants?
> 
> guard let/var newInstance = try throwingFuncReturns() catch {
> 	// can not fall through
> }
> 
> let newInstance: Type
> do newInstance = try throwingFuncReturns() catch {
> 	// if not returned from here or the newInstance is not set here the compiler should raise an error
> }
> 
> do try throwingFunc() catch {
> 	// can fall through if needed (this means I can ignore the error)
> }
> 
> -- 
> Adrian Zubarev
> Sent with Airmail
> 
> Am 5. Februar 2016 bei 19:40:46, Félix Cloutier (felixcca at yahoo.ca <mailto:felixcca at yahoo.ca>) schrieb:
> 
>> Unless you're declaring a variable with a single-statement try, there's no reason to disallow fallthrough.
>> 
>> Félix
>> 
>>> Le 5 févr. 2016 à 13:28:58, Adrian Zubarev via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> a écrit :
>>> 
>>> Right now the error handling mechanism will fall through even if the error was handled inside the catch scope.
>>> 
>>> enum Error: ErrorType {
>>> case SomeError
>>> }
>>> 
>>> func throwingFunc() throws {
>>> throw Error.SomeError
>>> }
>>> 
>>> func throwingFuncReturns() throws -> Int {
>>> return 0
>>> }
>>> 
>>> do { try throwingFunc() } catch {
>>> /* do nothing */
>>> }
>>> 
>>> var num: Int
>>> do { num = try throwingFuncReturns() } catch {
>>> /* do nothing */
>>> }
>>> 
>>> print("both fell through")
>>> 
>>> To guarantee the safety of the execution from my point of view the catch body may not fall through (for single try statements), at least not by default.
>>> 
>>> I think it is fine to rename the proposed mechanism.
>>> 
>>> do try throwingFunc() catch { 
>>> /* handle error */ 
>>> }
>>> 
>>> do try throwingFunc() catch _ { 
>>> /* handle error */ 
>>> }
>>> 
>>> do try throwingFunc() catch pattern { 
>>> /* handle error */ 
>>> }
>>> 
>>> do try throwingFunc() catch pattern { 
>>> /* handle error */ 
>>> }
>>> 
>>> do let newInstance = try throwingFuncReturns() catch catch pattern where condition { 
>>> /* handle error */ 
>>> }
>>> 
>>> do var newMutableInstance = try throwingFuncReturns() catch pattern where condition { 
>>> /* handle error */ 
>>> }
>>> 
>>> 'Where' clause is significant for catching wider range of errors that might occur.
>>> 
>>> -- 
>>> Adrian Zubarev
>>> Sent with Airmail
>>> 
>>> Am 5. Februar 2016 bei 19:05:12, Félix Cloutier (felixcca at yahoo.ca <mailto:felixcca at yahoo.ca>) schrieb:
>>> 
>>>> We could do it without a new guard syntax if `do` didn't need a block statement.
>>>> 
>>>> Félix
>>>> 
>>>>> Le 5 févr. 2016 à 12:54:47, Adrian Zubarev via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> a écrit :
>>>>> 
>>>>> Hello dear Swift community,
>>>>> 
>>>>> this proposal might seem like some new syntax sugar, but it also aims to escape the 'do { }' scope from the 'do try catch‘ mechanism while making the existing error handling more powerful.
>>>>> 
>>>>> Lets assume we have some sort of network type, which can throw a ton of different errors:
>>>>> 
>>>>> struct TCPListener {
>>>>> init(address: String) throws { /* implement */ }
>>>>> 
>>>>> func accept() throws -> TCPConn { /* implement */ }
>>>>> 
>>>>> /* ... */
>>>>> }
>>>>> 
>>>>> A way of implimentation might look like this:
>>>>> 
>>>>> let listener: TCPListener
>>>>> do {
>>>>> listener = try TCPListener("some valid address")
>>>>> 
>>>>> // we could do more work here, but if we need to catch more
>>>>> // errors we will result in a new PYRAMIDE OF DOOM
>>>>> } catch {
>>>>> fatalError()
>>>>> }
>>>>> 
>>>>> At this point think about the comment inside the 'do { }' scope. Such an application might result in a new pyramide of doom as we know from optional unwrapping before 'guard else' mechanism was introduced.
>>>>> 
>>>>> let clientConn: TCPConn
>>>>> do {
>>>>> clientConn = try listener.accept() // save to call accept method
>>>>> } catch {
>>>>> fatalError()
>>>>> } 
>>>>> 
>>>>> As you can see this application might not need the extra 'do' scope at all, and if it does, the 'do try catch' is still there.
>>>>> 
>>>>> I propose a new error handling mechanism that mimics the solution for optional pyramide of doom, which adds a slightly better syntax and removes the unneeded/unused 'do { }' scope (as for the example from above). Not only can this mechanism guarantee the execution of a throwing function without any errors (like a true guard condition) it also can assign returned values to a new constant/variable.
>>>>> 
>>>>> Introducing the 'guard try catch' mechanism:
>>>>> 
>>>>> guard try throwingFunc() catch { 
>>>>> /* handle error */ 
>>>>> }
>>>>> 
>>>>> guard try throwingFunc() catch _ { 
>>>>> /* handle error */ 
>>>>> }
>>>>> 
>>>>> guard try throwingFunc() catch pattern { 
>>>>> /* handle error */ 
>>>>> }
>>>>> 
>>>>> guard try throwingFunc() catch pattern where condition { 
>>>>> /* handle error */ 
>>>>> }
>>>>> 
>>>>> guard let newInstance = try throwingFuncReturns() catch ... { 
>>>>> /* handle error */ 
>>>>> }
>>>>> 
>>>>> Where '...' represents the different combinations of possible patterns already showed in the first 4 examples.
>>>>> 
>>>>> We also might want the return type to be mutable.
>>>>> 
>>>>> guard var newMutableInstance = try throwingFuncReturns() catch ... { 
>>>>> /* handle error */ 
>>>>> }
>>>>> 
>>>>> This mechanism also makes the error handling more powerful, since it can catch more specific errors defined with 'where condition'.
>>>>> 
>>>>> Lets rebuild the example from above with the new mechanism:
>>>>> 
>>>>> guard let listener = try TCPListener("some valid address") catch {
>>>>> fatalError()
>>>>> }
>>>>> 
>>>>> guard let clientConn = try listener.accept() catch {
>>>>> fatalError()
>>>>> } 
>>>>> 
>>>>> One think that should be mentioned here is that the method call from the second 'guard' is safe, because a 'guard' body may not fall through.
>>>>> 
>>>>> Impact on existing codebase: None, because the mechanism is new and does not break any existing code.
>>>>> 
>>>>> I'm really curious about your opinions.
>>>>> _______________________________________________
>>>>> swift-evolution mailing list
>>>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160205/2961a5ce/attachment.html>


More information about the swift-evolution mailing list