Idea: Extend "guard" to try-statements, with a catch block


(Jacob Bandes-Storch) #1

Guard statements interact with pattern-matching to ensure that control flow
cannot continue unless a pattern is matched. This is a very convenient way
to introduce non-optional variables from optional-valued expressions:

    // func foo() -> T? ...
    guard let x = foo() *else* {
        // the compiler does not allow control flow to escape this block
    }
    // control flow cannot reach here if foo() returned nil

Guard statements can be used with the optional form "*try?*" to provide the
same functionality for throwing functions:

    // func foo() throws -> T ...
    guard let x = *try?* foo() *else* {
        // the compiler does not allow control flow to escape this block
        // the "error" parameter is not available here
    }
    // control flow cannot reach here if foo() threw an error

However, the error that was thrown is not recoverable inside the "else"
block. A workaround is to add extra lines of code & indentation levels to
achieve this with do+catch:

    let x: T
    do {
        x = try foo()
    } catch {
        // control flow can escape this block, but the compiler won't allow
x to be used later if it's not initialized here
    }

*I propose extending guard-statements to handle errors* without using the
optional "try?" and without a do-block, by allowing the expression to
throw, and offering "catch" instead of "else":

    // func foo() throws -> T ...
    guard let x = *try* foo *catch* {
        print("the error was: \(*error*)") // the "error" parameter is
available here
        // the compiler does not allow control flow to escape this block
    }
    // control flow cannot reach here if foo() threw an error

We could allow the same sorts of catch blocks as normal do-blocks:

    guard let x = try foo() *catch let error as MyErrorType* {
        // handle specific error; control flow must not escape
    } *catch* {
        // fallback error case; control flow must not escape
    }

(Of course, we'd want to offer sensical error message / fix-it hints when
"else" was used with a throwing statement, or when "catch" was used with a
non-throwing statement.)

Thoughts?

Here are some discussion topics:

- If Swift's error-handling mechanisms evolved into a first-class Result
type, would this proposal be moot?

- Would this make sense as a feature of pattern-matching, rather than just
"guard", so you could also do "if case let x = try foo() { ... } catch {
... }" ?

Jacob


Guard statements should allow you to catch errors via a catch block
#2

Thoughts?

Thanks for writing this up! I think it's a natural step to take from the current model.

Here are some discussion topics:

- If Swift's error-handling mechanisms evolved into a first-class Result type, would this proposal be moot?

Could you clarify what you mean by this?

- Would this make sense as a feature of pattern-matching, rather than just "guard", so you could also do "if case let x = try foo() { ... } catch { ... }" ?

This makes sense to me at first glance, though it makes things rather complicated.

  - Can `catch` blocks appear before `else [if]` blocks? E.g.,

        if try test() != nil {
            ...
        } catch {
            ...
        } else if try test2() != nil {
            ...
        } else {
            ...
        } catch {
            ...
        }

  - Are `if`/`else` blocks generally promoted to `do`-like state when `catch` blocks exist?

        if something() {
            try failable()
        } catch {
            ...
        }

  - Does this extend to `switch` and how?

···

On Feb 29, 2016, at 3:09 PM, Jacob Bandes-Storch via swift-evolution <swift-evolution@swift.org> wrote:

--
Stephen


(Chris Lattner) #3

I don’t think that this syntax makes sense, because you’re changing "guard let x = ” to not test an optional. The syntax you’re searching for seems more consistent as a modifier on var/let itself:

// func foo() throws -> T ...
let x = try foo() catch {
        print("the error was: \(error)") // the "error" parameter is available here
        // the compiler does not allow control flow to escape this block
}

The guard form of this would still make sense, but the existing “guard let” and “guard case” matching should work as it does. For example, something like this should be allowed:

// func bar() throws -> T?
guard let x = try bar() else {
        // this runs if ‘bar’ returns nil.
        // the compiler does not allow control flow to escape this block
} catch {
        print("the error was: \(error)") // the "error" parameter is available here
        // the compiler does not allow control flow to escape this block
}

More generally, the “guard” form would be workable on anything that takes a stmt-condition. This brings “if" and “while” into the mix.

-Chris

···

On Feb 29, 2016, at 12:09 PM, Jacob Bandes-Storch via swift-evolution <swift-evolution@swift.org> wrote:

I propose extending guard-statements to handle errors without using the optional "try?" and without a do-block, by allowing the expression to throw, and offering "catch" instead of "else":

    // func foo() throws -> T ...
    guard let x = try foo catch {
        print("the error was: \(error)") // the "error" parameter is available here
        // the compiler does not allow control flow to escape this block
    }
    // control flow cannot reach here if foo() threw an error


(Jacob Bandes-Storch) #4

Thanks for the feedback, Stephen.

>
> - If Swift's error-handling mechanisms evolved into a first-class Result
type, would this proposal be moot?

Could you clarify what you mean by this?

I didn't think through it very far, which is why I put it under "discussion
topics" rather than fleshing it out :slight_smile:

I guess I'm just wondering whether the syntax for optionals/errors might
change significantly enough with the introduction of Result that it
wouldn't make sense to introduce this feature now.

Suppose we had "enum Result<T> { case Some(T), Error(ErrorType) }". Then
"throws" might be syntactic sugar: "foo() throws -> T" would be the same as
"foo() -> Result<T>". Using today's syntax, you could handle Results using
switch:

    switch foo() {
    case .Some(let x):
        // use x
    case .Error(let error):
        // use error
    }

But conceivably the Result would be treated with more first-class syntax:

    if let x = try foo() {
        // handle .Some
    } catch {
        // handle .Error
    }

The more I think about this, it doesn't seem too different from what I
proposed, so I guess it's not worth worrying about conflicts.

> - Would this make sense as a feature of pattern-matching, rather than
just "guard", so you could also do "if case let x = try foo() { ... } catch
{ ... }" ?

This makes sense to me at first glance, though it makes things rather
complicated.

  - Can `catch` blocks appear before `else [if]` blocks? E.g.,

        if try test() != nil {
            ...
        } catch {
            ...
        } else if try test2() != nil {
            ...
        } else {
            ...
        } catch {
            ...
        }

That's an interesting point. I can't see why this shouldn't be allowed.
Would you be able to handle all errors at the end?

    e.g.: if case let x = try foo() { ... } else if case let y = try bar()
{ ... } catch { /*handle errors from both expressions*/ }

  - Are `if`/`else` blocks generally promoted to `do`-like state when
`catch` blocks exist?

        if something() {
            try failable()
        } catch {
            ...
        }

Hmm. I think I would like this, but there may have been reasons for not
allowing this sort of thing. I can't recall if it's been discussed before.
What do others think?

  - Does this extend to `switch` and how?

Maybe just the same thing: "switch try foo() { ... } catch { ... }" ?

···

On Mon, Feb 29, 2016 at 1:03 PM, Stephen Celis <stephen.celis@gmail.com> wrote:

> On Feb 29, 2016, at 3:09 PM, Jacob Bandes-Storch via swift-evolution < > swift-evolution@swift.org> wrote:


(Jacob Bandes-Storch) #5

Thanks for the feedback, Chris.

To clarify, are you suggesting that plain variable bindings would support
this too (such as "let x = try foo() catch { ... }")? I like this idea. I
can also foresee the desire for something like this:

    let x = try foo() catch {
        print(error)
        x = bar() // by some syntax, provide a fallback value for x,
since foo() threw
        // control flow *can* escape, since x has a value
    }

Of course, you can achieve this today with "let x; do { x = try foo() }
catch { x = bar() }", or with "let x = try? foo() ?? bar()". I just wonder
if it's worth considering the possibility that this new feature would allow
control flow to escape in some cases. (After all, control flow can exit the
"catch" block we have today.) But it's also nice to be able to glance at
the code and see that, unambiguously, control flow can't escape the block.

The reason I originally suggested "guard" is readability: Seeing the word
"guard", a reader knows that control flow can't escape the else/catch
block. But I understand why guard is still necessary for working with
optionals/patterns, and I suppose seeing "try" introducing the expression
may be enough.

Do you have thoughts on whether else/catch blocks should be re-orderable?

Another question: should it work with expressions that don't bind
variables? Simply "try foo() catch { ... }" ? (At one point I had
considered "*do* try ... catch ...", as a braceless analogue of "*do* { try
... } catch ...".)

Jacob

···

On Mon, Feb 29, 2016 at 10:34 PM, Chris Lattner <clattner@apple.com> wrote:

On Feb 29, 2016, at 12:09 PM, Jacob Bandes-Storch via swift-evolution < > swift-evolution@swift.org> wrote:

*I propose extending guard-statements to handle errors* without using the
optional "try?" and without a do-block, by allowing the expression to
throw, and offering "catch" instead of "else":

    // func foo() throws -> T ...
    guard let x = *try* foo *catch* {
        print("the error was: \(*error*)") // the "error" parameter is
available here
        // the compiler does not allow control flow to escape this block
    }
    // control flow cannot reach here if foo() threw an error

I don’t think that this syntax makes sense, because you’re changing "guard
let x = ” to not test an optional. The syntax you’re searching for seems
more consistent as a modifier on var/let itself:

// func foo() throws -> T ...
let x = try foo() catch {
        print("the error was: \(error)") // the "error" parameter is
available here
        // the compiler does not allow control flow to escape this block
}

The guard form of this would still make sense, but the existing “guard
let” and “guard case” matching should work as it does. For example,
something like this should be allowed:

// func bar() throws -> T?
guard let x = try bar() else {
        // this runs if ‘bar’ returns nil.
        // the compiler does not allow control flow to escape this block
} catch {
        print("the error was: \(error)") // the "error" parameter is
available here
        // the compiler does not allow control flow to escape this block
}

More generally, the “guard” form would be workable on anything that takes
a stmt-condition. This brings “if" and “while” into the mix.

-Chris


(Jordan Rose) #6

Another possible syntax:

guard try let x = foo()
catch let error as FooError {
  return nil
}

Downsides:
- You can't limit the 'try' to only part of the expression.
- Actually catching a specific error makes for a very long line, to the point where I automatically wrapped it anyway. Maybe we should just allow one catch.
- It still feels like it should be testing for an optional (because of the binding syntax).

Jordan

···

On Mar 8, 2016, at 11:37, Jacob Bandes-Storch via swift-evolution <swift-evolution@swift.org> wrote:

Thanks for the feedback, Chris.

To clarify, are you suggesting that plain variable bindings would support this too (such as "let x = try foo() catch { ... }")? I like this idea. I can also foresee the desire for something like this:

    let x = try foo() catch {
        print(error)
        x = bar() // by some syntax, provide a fallback value for x, since foo() threw
        // control flow *can* escape, since x has a value
    }

Of course, you can achieve this today with "let x; do { x = try foo() } catch { x = bar() }", or with "let x = try? foo() ?? bar()". I just wonder if it's worth considering the possibility that this new feature would allow control flow to escape in some cases. (After all, control flow can exit the "catch" block we have today.) But it's also nice to be able to glance at the code and see that, unambiguously, control flow can't escape the block.

The reason I originally suggested "guard" is readability: Seeing the word "guard", a reader knows that control flow can't escape the else/catch block. But I understand why guard is still necessary for working with optionals/patterns, and I suppose seeing "try" introducing the expression may be enough.

Do you have thoughts on whether else/catch blocks should be re-orderable?

Another question: should it work with expressions that don't bind variables? Simply "try foo() catch { ... }" ? (At one point I had considered "do try ... catch ...", as a braceless analogue of "do { try ... } catch ...".)

Jacob

On Mon, Feb 29, 2016 at 10:34 PM, Chris Lattner <clattner@apple.com <mailto:clattner@apple.com>> wrote:
On Feb 29, 2016, at 12:09 PM, Jacob Bandes-Storch via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I propose extending guard-statements to handle errors without using the optional "try?" and without a do-block, by allowing the expression to throw, and offering "catch" instead of "else":

    // func foo() throws -> T ...
    guard let x = try foo catch {
        print("the error was: \(error)") // the "error" parameter is available here
        // the compiler does not allow control flow to escape this block
    }
    // control flow cannot reach here if foo() threw an error

I don’t think that this syntax makes sense, because you’re changing "guard let x = ” to not test an optional. The syntax you’re searching for seems more consistent as a modifier on var/let itself:

// func foo() throws -> T ...
let x = try foo() catch {
        print("the error was: \(error)") // the "error" parameter is available here
        // the compiler does not allow control flow to escape this block
}

The guard form of this would still make sense, but the existing “guard let” and “guard case” matching should work as it does. For example, something like this should be allowed:

// func bar() throws -> T?
guard let x = try bar() else {
        // this runs if ‘bar’ returns nil.
        // the compiler does not allow control flow to escape this block
} catch {
        print("the error was: \(error)") // the "error" parameter is available here
        // the compiler does not allow control flow to escape this block
}

More generally, the “guard” form would be workable on anything that takes a stmt-condition. This brings “if" and “while” into the mix.

-Chris

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


(Chris Lattner) #7

Thanks for the feedback, Chris.

To clarify, are you suggesting that plain variable bindings would support this too (such as "let x = try foo() catch { ... }”)?

I was responding to this in your original proposal:

    // func foo() throws -> T ...
    guard let x = try foo catch {

This is inconsistent with what we currently have, because “if let” and “guard let” match against an optional and succeed iff the optional is present. You were seemingly saying that the presence of “catch” later in the statement would affect this very primitive behavior that we have.

I’m not a strong believer in this proposal, because it is adding complexity and sugar, but I haven’t seen strong motivation that it is common. This isn’t to say that it isn’t common and worthwhile, just that I haven’t seen any evidence.

I like this idea. I can also foresee the desire for something like this:

    let x = try foo() catch {
        print(error)
        x = bar() // by some syntax, provide a fallback value for x, since foo() threw
        // control flow *can* escape, since x has a value
    }

We wouldn’t do that though, we’d require guard-like behavior for the same reason we require it for guard: the value of guard is that you *know* that an instance of guard doesn’t fall through, without analyzing its behavior in depth.

Of course, you can achieve this today

Yes, your proposal is a sugar proposal.

with "let x; do { x = try foo() } catch { x = bar() }", or with "let x = try? foo() ?? bar()". I just wonder if it's worth considering the possibility that this new feature would allow control flow to escape in some cases. (After all, control flow can exit the "catch" block we have today.) But it's also nice to be able to glance at the code and see that, unambiguously, control flow can't escape the block.

The reason I originally suggested "guard" is readability: Seeing the word "guard", a reader knows that control flow can't escape the else/catch block. But I understand why guard is still necessary for working with optionals/patterns, and I suppose seeing "try" introducing the expression may be enough.

This shouldn’t be tied to the presence of try, because it already means something (that the enclosed expression can throw). This:

guard let x = try foo() …

Already means “call foo, if it throws, propagate the error. If not, test the returned optional”. Changing that based on a subsequent ‘catch’ seems wrong.

Another question: should it work with expressions that don't bind variables? Simply "try foo() catch { ... }" ? (At one point I had considered "do try ... catch ...", as a braceless analogue of "do { try ... } catch ...”.)

In my opinion, this whole proposal could be better served with library functions.

-Chris

···

On Mar 8, 2016, at 11:37 AM, Jacob Bandes-Storch <jtbandes@gmail.com> wrote:


(Adrian Zubarev) #8

This is the exact same topic I started over a moth ago: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160201/009296.html

As already discussed in my topic it’s better not to use `guard` in this case, but add a new single statement `try catch` mechanism.

func scope() {
  let foo: Int? = try throwingFuncReturns() catch {
    // foo can’t be available here, because its
    // declared on the same line
    // by default you can fall through the catch block
  }
  // using foo would not be save here
  guard let unwrappedFoo = foo else {
    return
  }
  use(unwrappedFoo)
}
On way to solve this problem for throwing functions that return a value could look like this:
func scope() {
  let foo: Int?
  foo = try throwingFuncReturns() catch {
    // foo is available here
    // make sure to initialize foo here if you do not return from here
  }
  // foo is save here
  guard let unwrappedFoo = foo else {
    return
  }
  use(unwrappedFoo)
}
And because we just can’t use a single `try catch` statement like we would wish, we can bring back the `do` keyword.
func scope() {
  let foo: Int?
  do foo = try throwingFuncReturns() catch {
    // init foo (+ more optional work) or return
  }
  guard let unwrappedFoo = foo else {
    return
  }
  use(unwrappedFoo)
}
This is the best solution I could come up with, and it does work fine with functions that do not return a value:
func scope() {
  let foo: Int?
  do try catchMeIfYouCan() catch {
    // do some work -> fallthough or return
  }
}
But from my personal point of view I would remove the `do` keyword in single `try catch` statements. The `do` keyboard is only needed for the `do body` if there are more lines of code to process before catching an error.
A single `do catch` mechanism would be good for escaping another pyramid of doom like we had with `if` before the `guard` mechanism was introduced.

···

--
Adrian Zubarev

Am 8. März 2016 bei 20:37:52, Jacob Bandes-Storch via swift-evolution (swift-evolution@swift.org) schrieb:

Thanks for the feedback, Chris.

To clarify, are you suggesting that plain variable bindings would support this too (such as "let x = try foo() catch { ... }")? I like this idea. I can also foresee the desire for something like this:

let x = try foo\(\) catch \{
    print\(error\)
    x = bar\(\)   // by some syntax, provide a fallback value for x, since foo\(\) threw
    // control flow \*can\* escape, since x has a value
\}

Of course, you can achieve this today with "let x; do { x = try foo() } catch { x = bar() }", or with "let x = try? foo() ?? bar()". I just wonder if it's worth considering the possibility that this new feature would allow control flow to escape in some cases. (After all, control flow can exit the "catch" block we have today.) But it's also nice to be able to glance at the code and see that, unambiguously, control flow can't escape the block.

The reason I originally suggested "guard" is readability: Seeing the word "guard", a reader knows that control flow can't escape the else/catch block. But I understand why guard is still necessary for working with optionals/patterns, and I suppose seeing "try" introducing the expression may be enough.

Do you have thoughts on whether else/catch blocks should be re-orderable?

Another question: should it work with expressions that don't bind variables? Simply "try foo() catch { ... }" ? (At one point I had considered "do try ... catch ...", as a braceless analogue of "do { try ... } catch ...".)

Jacob

On Mon, Feb 29, 2016 at 10:34 PM, Chris Lattner <clattner@apple.com> wrote:
On Feb 29, 2016, at 12:09 PM, Jacob Bandes-Storch via swift-evolution <swift-evolution@swift.org> wrote:
I propose extending guard-statements to handle errors without using the optional "try?" and without a do-block, by allowing the expression to throw, and offering "catch" instead of "else":

// func foo\(\) throws \-&gt; T \.\.\.
guard let x = try foo catch \{
    print\(&quot;the error was: \\\(error\)&quot;\)  // the &quot;error&quot; parameter is available here
    // the compiler does not allow control flow to escape this block
\}
// control flow cannot reach here if foo\(\) threw an error

I don’t think that this syntax makes sense, because you’re changing "guard let x = ” to not test an optional. The syntax you’re searching for seems more consistent as a modifier on var/let itself:

// func foo() throws -> T ...
let x = try foo() catch {
print("the error was: \(error)") // the "error" parameter is available here
// the compiler does not allow control flow to escape this block
}

The guard form of this would still make sense, but the existing “guard let” and “guard case” matching should work as it does. For example, something like this should be allowed:

// func bar() throws -> T?
guard let x = try bar() else {
// this runs if ‘bar’ returns nil.
// the compiler does not allow control flow to escape this block
} catch {
print("the error was: \(error)") // the "error" parameter is available here
// the compiler does not allow control flow to escape this block
}

More generally, the “guard” form would be workable on anything that takes a stmt-condition. This brings “if" and “while” into the mix.

-Chris

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


(Erica Sadun) #9

Regardless of syntax, I'm still missing the point on this one.

If there's error-handling and recovery work to be done, isn't this better done without a guard statement, either using `try` to allow the calling chain to respond or using a `do`-`catch` construct. `guard` guarantees scope exit on failure, but so does `try` outside of a`do`-`catch`.

* If you care about just logging errors, you can use a printing form of `try?`. I brought this up on another thread.
* If you care about mitigating a failure, then you should give the `catch` clause the due space and ability to handle recovery that it deserves.
* If you don't care about printing the error, why not use normal `guard` and `try?`.

What is the point of only catching specific errors, or specific types of errors, or etc that deserves a language change here, where it's merged into `guard`?

-- E, dense

···

On Mar 8, 2016, at 4:56 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

Another possible syntax:

guard try let x = foo()
catch let error as FooError {
  return nil
}

Downsides:
- You can't limit the 'try' to only part of the expression.
- Actually catching a specific error makes for a very long line, to the point where I automatically wrapped it anyway. Maybe we should just allow one catch.
- It still feels like it should be testing for an optional (because of the binding syntax).

Jordan

On Mar 8, 2016, at 11:37, Jacob Bandes-Storch via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Thanks for the feedback, Chris.

To clarify, are you suggesting that plain variable bindings would support this too (such as "let x = try foo() catch { ... }")? I like this idea. I can also foresee the desire for something like this:

    let x = try foo() catch {
        print(error)
        x = bar() // by some syntax, provide a fallback value for x, since foo() threw
        // control flow *can* escape, since x has a value
    }

Of course, you can achieve this today with "let x; do { x = try foo() } catch { x = bar() }", or with "let x = try? foo() ?? bar()". I just wonder if it's worth considering the possibility that this new feature would allow control flow to escape in some cases. (After all, control flow can exit the "catch" block we have today.) But it's also nice to be able to glance at the code and see that, unambiguously, control flow can't escape the block.

The reason I originally suggested "guard" is readability: Seeing the word "guard", a reader knows that control flow can't escape the else/catch block. But I understand why guard is still necessary for working with optionals/patterns, and I suppose seeing "try" introducing the expression may be enough.

Do you have thoughts on whether else/catch blocks should be re-orderable?

Another question: should it work with expressions that don't bind variables? Simply "try foo() catch { ... }" ? (At one point I had considered "do try ... catch ...", as a braceless analogue of "do { try ... } catch ...".)

Jacob

On Mon, Feb 29, 2016 at 10:34 PM, Chris Lattner <clattner@apple.com <mailto:clattner@apple.com>> wrote:
On Feb 29, 2016, at 12:09 PM, Jacob Bandes-Storch via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I propose extending guard-statements to handle errors without using the optional "try?" and without a do-block, by allowing the expression to throw, and offering "catch" instead of "else":

    // func foo() throws -> T ...
    guard let x = try foo catch {
        print("the error was: \(error)") // the "error" parameter is available here
        // the compiler does not allow control flow to escape this block
    }
    // control flow cannot reach here if foo() threw an error

I don’t think that this syntax makes sense, because you’re changing "guard let x = ” to not test an optional. The syntax you’re searching for seems more consistent as a modifier on var/let itself:

// func foo() throws -> T ...
let x = try foo() catch {
        print("the error was: \(error)") // the "error" parameter is available here
        // the compiler does not allow control flow to escape this block
}

The guard form of this would still make sense, but the existing “guard let” and “guard case” matching should work as it does. For example, something like this should be allowed:

// func bar() throws -> T?
guard let x = try bar() else {
        // this runs if ‘bar’ returns nil.
        // the compiler does not allow control flow to escape this block
} catch {
        print("the error was: \(error)") // the "error" parameter is available here
        // the compiler does not allow control flow to escape this block
}

More generally, the “guard” form would be workable on anything that takes a stmt-condition. This brings “if" and “while” into the mix.

-Chris

_______________________________________________
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


#10

How about only allowing "guard try? catch":

    guard let x = try? foo() catch { ... }

This would address both concerns of Chris:

• "This is inconsistent with what we currently have, because “if let” and “guard let” match against an optional and succeed iff the optional is present."

• "This shouldn’t be tied to the presence of try, because it already means something (that the enclosed expression can throw). This:
        guard let x = try foo() …
Already means “call foo, if it throws, propagate the error. If not, test the returned optional”."

With "try?" it is clear that "foo()" doesn't throw an error in the guard expression and guard matches against an optional. This makes it unambiguous to "guard try else" which throws in this case.

Kind regards
- Maximilian

···

Am 10.03.2016 um 05:32 schrieb Chris Lattner via swift-evolution <swift-evolution@swift.org>:

On Mar 8, 2016, at 11:37 AM, Jacob Bandes-Storch <jtbandes@gmail.com> wrote:

Thanks for the feedback, Chris.

To clarify, are you suggesting that plain variable bindings would support this too (such as "let x = try foo() catch { ... }”)?

I was responding to this in your original proposal:

    // func foo() throws -> T ...
    guard let x = try foo catch {

This is inconsistent with what we currently have, because “if let” and “guard let” match against an optional and succeed iff the optional is present. You were seemingly saying that the presence of “catch” later in the statement would affect this very primitive behavior that we have.

I’m not a strong believer in this proposal, because it is adding complexity and sugar, but I haven’t seen strong motivation that it is common. This isn’t to say that it isn’t common and worthwhile, just that I haven’t seen any evidence.

I like this idea. I can also foresee the desire for something like this:

    let x = try foo() catch {
        print(error)
        x = bar() // by some syntax, provide a fallback value for x, since foo() threw
        // control flow *can* escape, since x has a value
    }

We wouldn’t do that though, we’d require guard-like behavior for the same reason we require it for guard: the value of guard is that you *know* that an instance of guard doesn’t fall through, without analyzing its behavior in depth.

Of course, you can achieve this today

Yes, your proposal is a sugar proposal.

with "let x; do { x = try foo() } catch { x = bar() }", or with "let x = try? foo() ?? bar()". I just wonder if it's worth considering the possibility that this new feature would allow control flow to escape in some cases. (After all, control flow can exit the "catch" block we have today.) But it's also nice to be able to glance at the code and see that, unambiguously, control flow can't escape the block.

The reason I originally suggested "guard" is readability: Seeing the word "guard", a reader knows that control flow can't escape the else/catch block. But I understand why guard is still necessary for working with optionals/patterns, and I suppose seeing "try" introducing the expression may be enough.

This shouldn’t be tied to the presence of try, because it already means something (that the enclosed expression can throw). This:

guard let x = try foo() …

Already means “call foo, if it throws, propagate the error. If not, test the returned optional”. Changing that based on a subsequent ‘catch’ seems wrong.

Another question: should it work with expressions that don't bind variables? Simply "try foo() catch { ... }" ? (At one point I had considered "do try ... catch ...", as a braceless analogue of "do { try ... } catch ...”.)

In my opinion, this whole proposal could be better served with library functions.

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


(Erica Sadun) #11

I'm not a fan of the notion of guard/catch. However, it occurs to me that my "attempt" code may address this issue.
I've recently updated it to take an arbitrary error handler, which if omitted, simply prints the error. Otherwise it acts like try?
or if you set crashOnError, like try!. It works with guard.

-- E

github: https://github.com/erica/SwiftUtility/blob/master/Sources/CoreError.swift

public typealias CommonErrorHandlerType = (String, Int, ErrorType) -> Void

/// Replacement for `try?` that introduces an error handler
/// The default handler prints an error before returning nil
///
/// - Parameter file: source file, derived from `__FILE__` context literal
/// - Parameter line: source line, derived from `__LINE__` context literal
/// - Parameter crashOnError: defaults to false. When set to true
/// will raise a fatal error, emulating try! instead of try?
/// - Parameter errorHandler: processes the error, returns nil
///
/// ```swift
/// attempt {
/// let mgr = NSFileManager.defaultManager()
/// try mgr.createDirectoryAtPath(
/// "/Users/notarealuser",
/// withIntermediateDirectories: true,
/// attributes: nil)
/// }
/// ```
///
public func attempt<T>(
    file fileName: String = __FILE__,
    line lineNumber: Int = __LINE__,
    crashOnError: Bool = false,
    errorHandler: CommonErrorHandlerType = {
        // Default handler prints context:error and returns nil
        fileName, lineNumber, error in
        
        /// Retrieve last path component because #fileName is
        /// not yet a thing in Swift
        let trimmedFileName: String = (fileName as NSString).lastPathComponent
        
        /// Force print and return nil like try?
        print("Error \(trimmedFileName):\(lineNumber) \(error)")
    },
    closure: () throws -> T) -> T? {
        
        do {
            // Return executes only if closure succeeds, returning T
            return try closure()
            
        } catch {
            // Emulate try! by crashing
            if crashOnError {
                print("Fatal error \(fileName):\(lineNumber): \(error)")
                fatalError()
            }
            
            // Execute error handler and return nil
            errorHandler(fileName, lineNumber, error)
            return nil
        }
}

···

On Mar 14, 2016, at 10:20 AM, Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org> wrote:

How about only allowing "guard try? catch":

    guard let x = try? foo() catch { ... }

This would address both concerns of Chris:

• "This is inconsistent with what we currently have, because “if let” and “guard let” match against an optional and succeed iff the optional is present."

• "This shouldn’t be tied to the presence of try, because it already means something (that the enclosed expression can throw). This:
        guard let x = try foo() …
Already means “call foo, if it throws, propagate the error. If not, test the returned optional”."

With "try?" it is clear that "foo()" doesn't throw an error in the guard expression and guard matches against an optional. This makes it unambiguous to "guard try else" which throws in this case.

Kind regards
- Maximilian


#12

Your "attempt" function addresses this issue if someone only wants to print the error:

guard let x = attempt({ try throwingFunction(y) }) else {
         return
}
// prints "Error \(trimmedFileName):\(lineNumber) \(error)"

In contrast to

guard let x = try? throwingFunction(y) catch {
        // you have to make your own print
        return
}

···

---------------------

However if you want to handle the error before you exit the scope it is quite inconvenient:

guard let x = attempt({ _, _, error in /* handle error */ }, { try throwingFunction(y) } ) else {
        return
}

In contrast to:

guard let x = try? throwingFunction(y) catch {
        // handle error
        return
}

- Maximilian

Am 14.03.2016 um 18:00 schrieb Erica Sadun <erica@ericasadun.com>:

On Mar 14, 2016, at 10:20 AM, Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org> wrote:

How about only allowing "guard try? catch":

    guard let x = try? foo() catch { ... }

This would address both concerns of Chris:

• "This is inconsistent with what we currently have, because “if let” and “guard let” match against an optional and succeed iff the optional is present."

• "This shouldn’t be tied to the presence of try, because it already means something (that the enclosed expression can throw). This:
        guard let x = try foo() …
Already means “call foo, if it throws, propagate the error. If not, test the returned optional”."

With "try?" it is clear that "foo()" doesn't throw an error in the guard expression and guard matches against an optional. This makes it unambiguous to "guard try else" which throws in this case.

Kind regards
- Maximilian

I'm not a fan of the notion of guard/catch. However, it occurs to me that my "attempt" code may address this issue.
I've recently updated it to take an arbitrary error handler, which if omitted, simply prints the error. Otherwise it acts like try?
or if you set crashOnError, like try!. It works with guard.

-- E

github: https://github.com/erica/SwiftUtility/blob/master/Sources/CoreError.swift

public typealias CommonErrorHandlerType = (String, Int, ErrorType) -> Void

/// Replacement for `try?` that introduces an error handler
/// The default handler prints an error before returning nil
///
/// - Parameter file: source file, derived from `__FILE__` context literal
/// - Parameter line: source line, derived from `__LINE__` context literal
/// - Parameter crashOnError: defaults to false. When set to true
/// will raise a fatal error, emulating try! instead of try?
/// - Parameter errorHandler: processes the error, returns nil
///
/// ```swift
/// attempt {
/// let mgr = NSFileManager.defaultManager()
/// try mgr.createDirectoryAtPath(
/// "/Users/notarealuser",
/// withIntermediateDirectories: true,
/// attributes: nil)
/// }
/// ```
///
public func attempt<T>(
    file fileName: String = __FILE__,
    line lineNumber: Int = __LINE__,
    crashOnError: Bool = false,
    errorHandler: CommonErrorHandlerType = {
        // Default handler prints context:error and returns nil
        fileName, lineNumber, error in
        
        /// Retrieve last path component because #fileName is
        /// not yet a thing in Swift
        let trimmedFileName: String = (fileName as NSString).lastPathComponent
        
        /// Force print and return nil like try?
        print("Error \(trimmedFileName):\(lineNumber) \(error)")
    },
    closure: () throws -> T) -> T? {
        
        do {
            // Return executes only if closure succeeds, returning T
            return try closure()
            
        } catch {
            // Emulate try! by crashing
            if crashOnError {
                print("Fatal error \(fileName):\(lineNumber): \(error)")
                fatalError()
            }
            
            // Execute error handler and return nil
            errorHandler(fileName, lineNumber, error)
            return nil
        }
}


(Erica Sadun) #13

The error handler doesn't have to be co-linear or even defined in the same call:

/// consists of filename, line number, error tuple
public typealias CommonErrorHandlerType = (String, Int, ErrorType) -> Void

/// Default error handler prints context and error
public let defaultCommonErrorHandler: CommonErrorHandlerType = {
    filePath, lineNumber, error in
    let trimmedFileName: String = (filePath as NSString).lastPathComponent
    print("Error \(trimmedFileName):\(lineNumber) \(error)")
}

If you're doing much more than printing or adding a line-or-two extra then
I don't think you should be using guard, you should be applying do-catch at the call site
or using try (not try? or try!) and forwarding the error handling.

-- E

···

On Mar 23, 2016, at 8:05 AM, Maximilian Hünenberger <m.huenenberger@me.com> wrote:

Your "attempt" function addresses this issue if someone only wants to print the error:

guard let x = attempt({ try throwingFunction(y) }) else {
         return
}
// prints "Error \(trimmedFileName):\(lineNumber) \(error)"

In contrast to

guard let x = try? throwingFunction(y) catch {
        // you have to make your own print
        return
}

---------------------

However if you want to handle the error before you exit the scope it is quite inconvenient:

guard let x = attempt({ _, _, error in /* handle error */ }, { try throwingFunction(y) } ) else {
        return
}

In contrast to:

guard let x = try? throwingFunction(y) catch {
        // handle error
        return
}

- Maximilian

Am 14.03.2016 um 18:00 schrieb Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>>:

On Mar 14, 2016, at 10:20 AM, Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

How about only allowing "guard try? catch":

    guard let x = try? foo() catch { ... }

This would address both concerns of Chris:

• "This is inconsistent with what we currently have, because “if let” and “guard let” match against an optional and succeed iff the optional is present."

• "This shouldn’t be tied to the presence of try, because it already means something (that the enclosed expression can throw). This:
        guard let x = try foo() …
Already means “call foo, if it throws, propagate the error. If not, test the returned optional”."

With "try?" it is clear that "foo()" doesn't throw an error in the guard expression and guard matches against an optional. This makes it unambiguous to "guard try else" which throws in this case.

Kind regards
- Maximilian

I'm not a fan of the notion of guard/catch. However, it occurs to me that my "attempt" code may address this issue.
I've recently updated it to take an arbitrary error handler, which if omitted, simply prints the error. Otherwise it acts like try?
or if you set crashOnError, like try!. It works with guard.

-- E

github: https://github.com/erica/SwiftUtility/blob/master/Sources/CoreError.swift

public typealias CommonErrorHandlerType = (String, Int, ErrorType) -> Void

/// Replacement for `try?` that introduces an error handler
/// The default handler prints an error before returning nil
///
/// - Parameter file: source file, derived from `__FILE__` context literal
/// - Parameter line: source line, derived from `__LINE__` context literal
/// - Parameter crashOnError: defaults to false. When set to true
/// will raise a fatal error, emulating try! instead of try?
/// - Parameter errorHandler: processes the error, returns nil
///
/// ```swift
/// attempt {
/// let mgr = NSFileManager.defaultManager()
/// try mgr.createDirectoryAtPath(
/// "/Users/notarealuser",
/// withIntermediateDirectories: true,
/// attributes: nil)
/// }
/// ```
///
public func attempt<T>(
    file fileName: String = __FILE__,
    line lineNumber: Int = __LINE__,
    crashOnError: Bool = false,
    errorHandler: CommonErrorHandlerType = {
        // Default handler prints context:error and returns nil
        fileName, lineNumber, error in
        
        /// Retrieve last path component because #fileName is
        /// not yet a thing in Swift
        let trimmedFileName: String = (fileName as NSString).lastPathComponent
        
        /// Force print and return nil like try?
        print("Error \(trimmedFileName):\(lineNumber) \(error)")
    },
    closure: () throws -> T) -> T? {
        
        do {
            // Return executes only if closure succeeds, returning T
            return try closure()
            
        } catch {
            // Emulate try! by crashing
            if crashOnError {
                print("Fatal error \(fileName):\(lineNumber): \(error)")
                fatalError()
            }
            
            // Execute error handler and return nil
            errorHandler(fileName, lineNumber, error)
            return nil
        }
}