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

Adrian Zubarev adrian.zubarev at devandartist.com
Fri Feb 5 13:33:48 CST 2016


@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")
}

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) 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) 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> 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) 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> 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
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/0276fa8b/attachment-0001.html>


More information about the swift-evolution mailing list