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


(Adrian Zubarev) #1

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.


Guard statements should allow you to catch errors via a catch block
Shorthand ternary and switch assignments
(Erica Sadun) #2

I am having problems trying to understand why you'd want to circumvent the throwing chain and perform error handling and recovery late in the call tree rather than at the root point that initiated the request.

-- Erica

···

On Feb 5, 2016, at 10:54 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

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@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Félix Cloutier) #3

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@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@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Adrian Zubarev) #4

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@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@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(David Hart) #5

The only reason to favour guard is to have the compiler force you to exit the containing scope.

···

On 05 Feb 2016, at 19:05, Félix Cloutier via swift-evolution <swift-evolution@swift.org> wrote:

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@swift.org <mailto:swift-evolution@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@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Félix Cloutier) #6

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@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@yahoo.ca <mailto:felixcca@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@swift.org <mailto:swift-evolution@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@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Adrian Zubarev) #7

+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@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@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@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@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Félix Cloutier) #8

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@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@yahoo.ca <mailto:felixcca@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@swift.org <mailto:swift-evolution@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@yahoo.ca <mailto:felixcca@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@swift.org <mailto:swift-evolution@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@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Adrian Zubarev) #9

@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@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@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@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@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@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Adrian Zubarev) #10

@Félix as far as I know you won’t be able to fall through be accident:

func throwingFuncReturns() throws -> Int? {
  return 42
}

func use(foo: Int) {
  print(foo)
}

func scope() {
    
  let foo: Int? // saving will work as long as the type is an optional too

  do { foo = try throwingFuncReturns() } catch {

    // INITIALIZE foo OR RETURN <-- copiler will be happy
  }
  guard let unwrappedFoo = foo else {
    return
  }
  use(unwrappedFoo)
}

scope() // to be able to return

This was added in Swift 1.2 for `if else` as far as I know.

let x: SomeThing
if condition {
  x = foo()
} else {
  x = bar()
}
use(x)

So porting this behavior to a single `do try catch` will work as it did before:

func scope() {
    
  let foo: Int? // saving will work as long as the type is an optional too

  do foo = try throwingFuncReturns() catch {
    // INITIALIZE foo OR RETURN <-- copiler will be happy
  }
  guard let unwrappedFoo = foo else {
    return
  }
  use(unwrappedFoo)
}

Simplifying everything with the `guard` syntax might help us with optionals, but it automatically will close the door for `fallthrough`, because the original mechanism does not work this way. This will just confuse everyone.

It might sound absurd, but what if we remove the `do` keyword from the single `do try catch` and leave it as `try catch` with the some new behavior:

func scope() {
    
  let foo: Int? = try throwingFuncReturns() catch {
    // foo IS AVAILABLE INSIDE THE CATCH BODY
    // INITIALIZE foo OR RETURN
  }
  guard let unwrappedFoo = foo else {
    return
  }
  use(unwrappedFoo)
}

The `do` body is needed if we want to execute more than one operation from its body, but it isn’t needed for a single `try catch` mechanism at all. So why would we want to keep the keyword here? We can omit the type from the throwing function that returns a value if no errors occurred. Plus we could apply an extra rule and assign the value from the catch body then fall through or return/break.

This also won’t have any impact on existing codebase.


#11

@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

···

Le 5 févr. 2016 à 14:33, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> a écrit :

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@devandartist.com <mailto:adrian.zubarev@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@yahoo.ca <mailto:felixcca@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@swift.org <mailto:swift-evolution@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@yahoo.ca <mailto:felixcca@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@swift.org <mailto:swift-evolution@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@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Thorsten Seitz) #12

Having foo available within the catch would be very strange as it is not yet in scope.

-Thorsten

···

Am 07.02.2016 um 11:00 schrieb Adrian Zubarev via swift-evolution <swift-evolution@swift.org>:

@Félix as far as I know you won’t be able to fall through be accident:

func throwingFuncReturns() throws -> Int? {
  return 42
}

func use(foo: Int) {
  print(foo)
}

func scope() {
  
  let foo: Int? // saving will work as long as the type is an optional too

  do { foo = try throwingFuncReturns() } catch {

    // INITIALIZE foo OR RETURN <-- copiler will be happy
  }
  guard let unwrappedFoo = foo else {
    return
  }
  use(unwrappedFoo)
}

scope() // to be able to return

This was added in Swift 1.2 for `if else` as far as I know.

let x: SomeThing
if condition {
  x = foo()
} else {
  x = bar()
}
use(x)

So porting this behavior to a single `do try catch` will work as it did before:

func scope() {
  
  let foo: Int? // saving will work as long as the type is an optional too

  do foo = try throwingFuncReturns() catch {
    // INITIALIZE foo OR RETURN <-- copiler will be happy
  }
  guard let unwrappedFoo = foo else {
    return
  }
  use(unwrappedFoo)
}

Simplifying everything with the `guard` syntax might help us with optionals, but it automatically will close the door for `fallthrough`, because the original mechanism does not work this way. This will just confuse everyone.

It might sound absurd, but what if we remove the `do` keyword from the single `do try catch` and leave it as `try catch` with the some new behavior:

func scope() {
  
  let foo: Int? = try throwingFuncReturns() catch {
    // foo IS AVAILABLE INSIDE THE CATCH BODY
    // INITIALIZE foo OR RETURN
  }
  guard let unwrappedFoo = foo else {
    return
  }
  use(unwrappedFoo)
}

The `do` body is needed if we want to execute more than one operation from its body, but it isn’t needed for a single `try catch` mechanism at all. So why would we want to keep the keyword here? We can omit the type from the throwing function that returns a value if no errors occurred. Plus we could apply an extra rule and assign the value from the catch body then fall through or return/break.

This also won’t have any impact on existing codebase.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Adrian Zubarev) #13

It is possible in a lighter project, but its no fun to build an ErrorType for this: http://www.opensource.apple.com/source/xnu/xnu-1456.1.26/bsd/sys/errno.h

Here is a snippet from my current project I’m working on:

public enum TCPBug: ErrorType {
    
  case NetworkError(code: Int32, message: String)
  case FileError(code: Int32, message: String)
    
  @noreturn private static func raise(type: TCPBugType) throws {
      
    let errorCode = errno
    let errorMessage = String.fromCString(strerror(errorCode))!
      
    switch type {
        
    case .Network:
      throw TCPBug.NetworkError(code: errorCode, message: errorMessage)
    case .File:
      throw TCPBug.FileError(code: errorCode, message: errorMessage)
    }
  }
}

As you might guess from errno.h there are a lot a different errors that could be thrown. And as I described in my previous post I could handle errors where they occur, so there won’t be any new pyramid.

Anyways, from my perspective Swift does need a single 'do try catch‘ mechanism which escapes the 'do { }‘ scope. The only one problem we found is that declaring a new constant/variable inside a single ‘do try catch’ statement can not fall through from the catch body (like the 'guard' mechanism).

Best regards,
Adrian Zubarev