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

Dany St-Amant dsa.mls at icloud.com
Fri Feb 5 21:40:03 CST 2016


> Le 5 févr. 2016 à 14:33, Adrian Zubarev via swift-evolution <swift-evolution at swift.org> a écrit :
> 
> @Erica I wrote this example before:
> 
> do {
> 	let listener = try TCPListener("some address")
> 
> 	do {
> 		let conn = try listener.accept()
> 
> 		// do some work and catch more errors
> 
> 		do {
> 			try conn.close()
> 		} catch {
> 			do {
> 				try listener.close()
> 			} catch {
> 				fatallError("cannot close listener")
> 			}
> 			fatallError("cannot close connection")
> 		}
> 	} catch {
> 		fatallError("cannot accept")
> 	}
> } catch {
> 	fatallError("cannot create listener")
> }
> 

I am no expert (and have always be scared of throws) but from what I understand the errors thrown by each method can be specific, so you can do multiple try in a single do (even though I could not find example on the web); flattening your pyramid a little bit.

do {
    let listener = try TCPListener("some address")
    let conn = try listener.accept()
    // Some more code
} catch SomeError.CannotCreate {
    fatallError("cannot create listener")
} catch SomeError.Rejection {
    fatallError("cannot accept")
} catch {
    fatallError("Unknown")
}

So once restructured, is the pyramid still that bad?

Dany

> Than I realized I could avoid this pyramide of doom by doing it differently:
> 
> let listener: TCPListener
> do {
> 	listener = try TCPListener("some address")
> } catch {
> 	fatallError("cannot create listener")
> }
> 
> let conn: TCPConn
> do {
> 	conn = try listener.accept()
> } catch {
> 	fatallError("cannot accept")
> }
> 
> // do some work and catch more errors
> 
> do {
> 	try conn.close()
> } catch {
> 	do {
> 		try listener.close()
> 	} catch {
> 		fatallError("cannot close listener")
> 	}
> 	fatallError("cannot close connection")
> }
> 
> As you can see there is no need for the do block here. A single try-statement would do enough.
> 
> I could rewrite this example like this:
> 
> guard let listener = try TCPListener("some address") catch {
> 	fatallError("cannot create listener") // or return and try again later
> }
> 
> guard let conn = try listener.accept() catch {
> 	fatallError("cannot accept")
> }
> 
> // do some work and catch more errors
> 
> do try conn.close() catch {
> 	do try listener.close() catch {
> 		fatallError("cannot close listener")
> 	}
> 	fatallError("cannot close connection")
> }
> 
> -- 
> Adrian Zubarev
> Sent with Airmail
> 
> Am 5. Februar 2016 bei 20:18:26, Adrian Zubarev (adrian.zubarev at devandartist.com <mailto:adrian.zubarev at devandartist.com>) schrieb:
> 
>> +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
> 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/c0986d6e/attachment.html>


More information about the swift-evolution mailing list