Guaranteed closure execution

I'd like to pitch this proposal to implement the feature: https://github.com/zneak/swift-evolution/blob/master/proposals/00xx-noescape-once.md

Rationale for some points:

Only one closure parameter can be marked as @noescape(once) in a function signature.

The attribute doesn't specify the order of execution of the closures, so it could have unintended consequences if closure B depends on closure A but closure B is called first. Given the typical use case (the @noescape(once) closure as a trailing closure), I don't think that it's worth it to invest a lot of effort into coming up with a model to decide (and enforce) which closure has to be called first and what to do if either closure throws or something.

I don’t see a reason to have this limitation. Definitive initialization has to be able to generate conditional code for partially initialized cases anyway, e.g.:

var c : C

if … {
   c = C()
}
c = C() // could be an initialization or an assignment.
use(c)

The caller side would just have to conservatively prove that the closure bodies initialized any values they touch before using them (or that they were initialized already at the call site).

it is not required to be executed on a code path that throws;

It may need to be clarified into "must" or "must not”,

Yes, I agree it needs to be one or the other.

but I can't think about very good examples supporting either case right now.

I think the simplest model is that it should be “must”. IMO, the only most common (and again, simplest) semantics here is that the closure is called exactly once on any path to a return or throw.

-Chris

···

On Jan 30, 2016, at 10:17 PM, Félix Cloutier via swift-evolution <swift-evolution@swift.org> wrote:

The compiler needs a guarantee one way or the other (that the closure is called or that it is not called), because it needs to know whether to clean up values initialized by the closure.

-Chris

···

On Jan 31, 2016, at 9:54 AM, Dany St-Amant via swift-evolution <swift-evolution@swift.org> wrote:

it is not required to be executed on a code path that throws;

It may need to be clarified into "must" or "must not", but I can't think about very good examples supporting either case right now.

What is the implication of this @noescape(once) closure not being call on throws when the caller use try? variation?

To simplify my explanations, let's have these functions:

func foo() {
  let bar: Int
  withNoEscape { bar = 1 }
}

func withNoEscape(@autoclosure(once) closure: () -> ()) { /* snip */ }

Looking back, I do think that there should be a way to exit from `withNoEscape` without calling the closure, so yes, throwing should imply that the closure wasn't executed. If it's possible that `foo` swallowed an error from a throwing `withNoEscape`, the compiler should assume that the variables within haven't been initialized:

func foo() {
  let bar: Int
  do {
    try withNoEscape { bar = 1 }
    // bar has been initialized
  }
  catch {
    // bar has not been initialized
  }
}

func foo() {
  let bar: Int
  try? withNoEscape { bar = 1 }
  // bar has not been initialized
}

func foo() {
  let bar: Int
  try! withNoEscape { bar = 1 }
  // bar has been initialized
}

Closures that can throw are harder since normal functions are allowed to catch an error from a closure. For a @noescape(once) closure, it has been partially executed only and it would be non-trivial to determine what has been executed and what hasn't. As I see it, the best solution would be to force any code path in `withNoEscape` where it catches an exception from the closure to throw to indicate failure (even if the closure partially ran).

Another solution is to prevent `withNoEscape` from throwing, and prevent `@noescape(once)` closures from throwing. It's less classy, but the current places in the standard library where it would be nice to have it do not throw and I have a hunch that most cases won't need to throw either. Recovering from throwing closures does add a lot of complexity that I'm not sure the feature will pay for in the long run.

Félix

···

Le 31 janv. 2016 à 12:54:27, Dany St-Amant via swift-evolution <swift-evolution@swift.org> a écrit :

Le 31 janv. 2016 à 01:17, Félix Cloutier via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

I'd like to pitch this proposal to implement the feature: https://github.com/zneak/swift-evolution/blob/master/proposals/00xx-noescape-once.md

Rationale for some points:

Only one closure parameter can be marked as @noescape(once) in a function signature.

The attribute doesn't specify the order of execution of the closures, so it could have unintended consequences if closure B depends on closure A but closure B is called first. Given the typical use case (the @noescape(once) closure as a trailing closure), I don't think that it's worth it to invest a lot of effort into coming up with a model to decide (and enforce) which closure has to be called first and what to do if either closure throws or something.

it is not required to be executed on a code path that throws;

It may need to be clarified into "must" or "must not", but I can't think about very good examples supporting either case right now.

What is the implication of this @noescape(once) closure not being call on throws when the caller use try? variation? Looks like the compiler will have to assume that the @escape(once) is both not called and not not called (sorry for the double-negative). For the try!, i think that the compiler could assume that the @escape(none) is called, as the process would crash otherwise anyway on the throw.

    var bad: Bool = true
    enum e: ErrorType {
        case Simple
    }
    func f(@noescape closure: () -> ()) throws {
        if (bad) { throw e.Simple }
        closure()
    }
    let x: Int // Not initialized
    try? f { x = 1 }
    print(x) // May still be uninitialized, compiler must generate error
    x = 2 // May have been initialized, compiler must generate error

This should probably be highlighted in the proposal as an intended limitation for the typical usage.
Another special case with try? that people may be unlikely to use, is:

    func g(@noescape closure: () -> ()) throws -> Int {
        if (bad) { throw e.Simple }
        closure()
        return 1
    }
    if let data = try? g({ x = 1 }) {
        print(x) // Guaranteed to be initialized
    }
    print(x) // May still be uninitialized, compiler must generate error
    x = 2 // May have been initialized, compiler must generate error

Not sure if this case will make the implementation more complex, it is why I’m mentioning it.

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

Rationale for some points:

Only one closure parameter can be marked as @noescape(once) in a function signature.

The attribute doesn't specify the order of execution of the closures, so it could have unintended consequences if closure B depends on closure A but closure B is called first. Given the typical use case (the @noescape(once) closure as a trailing closure), I don't think that it's worth it to invest a lot of effort into coming up with a model to decide (and enforce) which closure has to be called first and what to do if either closure throws or something.

I don’t see a reason to have this limitation. Definitive initialization has to be able to generate conditional code for partially initialized cases anyway, e.g.:

var c : C

if … {
   c = C()
}
c = C() // could be an initialization or an assignment.
use(c)

The caller side would just have to conservatively prove that the closure bodies initialized any values they touch before using them (or that they were initialized already at the call site).

I'm not sure I communicated the concern clearly. Here's an example:

func withNoEscape(@noescape(once) a: () -> (), @noescape(once) b: () -> ()) { /* snip */ }

func foo() {
  let a: Int
  let b: Int
  withNoEscape({ a = 4 }, { b = a + 2})
}

How does the compiler know that `a` has been assigned a value by the time that `b = a + 2` is executed? Nothing says that `withNoEscape` executes the two closures in "visual order".

Félix

···

Le 31 janv. 2016 à 23:41:59, Chris Lattner <clattner@apple.com> a écrit :

func foo() {
  let bar: Int
  withNoEscape { bar = 1 }
}

func withNoEscape(@autoclosure(once) closure: () -> ()) { /* snip */ }

Looking back, I do think that there should be a way to exit from `withNoEscape` without calling the closure, so yes, throwing should imply that the closure wasn't executed. If it's possible that `foo` swallowed an error from a throwing `withNoEscape`, the compiler should assume that the variables within haven't been initialized:

I’m glad to see an @autoclosure func in this thread. We will want to be able to use this feature with @autoclosure in addition to @noescape.

As far as exiting without calling the closure, I suggest `@noescape(once?)`. The `?` indicates the closure may or may not be called, but will not be called more than once.

This would be handy in the case of the short-circuiting boolean operators, for example:

@warn_unused_result func &&<T : BooleanType, U : BooleanType>(_ lhs: T, @autoclosure(once?) _ rhs: () throws -> U) rethrows -> Bool

-Matthew

Indeed if the second closure could be made ready for both assignment or initialization of `b`, as Chris says. But here the concern is different: can even `a + 2` be computed?

Could a solution be to allow several @noescape(once) closures, but to forbid such dependency between them?
  
  let a: Int
  let b: Int
  withNoEscape({ a = 4 }, { b = 2 }) // OK
  withNoEscape({ a = 4 }, { b = a }) // Compiler error

Let me try to find reasons why we may want to support several @noescape(once) closures. Let’s imagine the context behind a use case for this.

1. Maybe one closure depends on the execution of the other. But then closures would likely take or return values, and our `a` and `b` initializations above are more like unnecessary side effects, some kind of clever trick that we may not need to support with so much convenience.

2. The two closures are internally run with code between them, code that involves a stateful API in which the ordering of statements is very important. Think OpenGL. Think our beloved UIKit. Now this may deserve attention. Because some kind of external constraint imposes the exposure of two distinct closures. It’s not fancy because it wants, it’s fancy because it must.

What do you think?

Gwendal

···

Le 1 févr. 2016 à 06:12, Félix Cloutier via swift-evolution <swift-evolution@swift.org> a écrit :

Le 31 janv. 2016 à 23:41:59, Chris Lattner <clattner@apple.com> a écrit :

Rationale for some points:

Only one closure parameter can be marked as @noescape(once) in a function signature.

The attribute doesn't specify the order of execution of the closures, so it could have unintended consequences if closure B depends on closure A but closure B is called first. Given the typical use case (the @noescape(once) closure as a trailing closure), I don't think that it's worth it to invest a lot of effort into coming up with a model to decide (and enforce) which closure has to be called first and what to do if either closure throws or something.

I don’t see a reason to have this limitation. Definitive initialization has to be able to generate conditional code for partially initialized cases anyway, e.g.:

var c : C

if … {
  c = C()
}
c = C() // could be an initialization or an assignment.
use(c)

The caller side would just have to conservatively prove that the closure bodies initialized any values they touch before using them (or that they were initialized already at the call site).

I'm not sure I communicated the concern clearly. Here's an example:

func withNoEscape(@noescape(once) a: () -> (), @noescape(once) b: () -> ()) { /* snip */ }

func foo() {
  let a: Int
  let b: Int
  withNoEscape({ a = 4 }, { b = a + 2})
}

How does the compiler know that `a` has been assigned a value by the time that `b = a + 2` is executed? Nothing says that `withNoEscape` executes the two closures in "visual order ».

It wouldn’t know that, this would be a compile-time error. Each closure would only be able to use values defined before the call.

-Chris

···

On Jan 31, 2016, at 9:12 PM, Félix Cloutier <felixcca@yahoo.ca> wrote:

Le 31 janv. 2016 à 23:41:59, Chris Lattner <clattner@apple.com> a écrit :

Rationale for some points:

Only one closure parameter can be marked as @noescape(once) in a function signature.

The attribute doesn't specify the order of execution of the closures, so it could have unintended consequences if closure B depends on closure A but closure B is called first. Given the typical use case (the @noescape(once) closure as a trailing closure), I don't think that it's worth it to invest a lot of effort into coming up with a model to decide (and enforce) which closure has to be called first and what to do if either closure throws or something.

I don’t see a reason to have this limitation. Definitive initialization has to be able to generate conditional code for partially initialized cases anyway, e.g.:

var c : C

if … {
  c = C()
}
c = C() // could be an initialization or an assignment.
use(c)

The caller side would just have to conservatively prove that the closure bodies initialized any values they touch before using them (or that they were initialized already at the call site).

I'm not sure I communicated the concern clearly. Here's an example:

func withNoEscape(@noescape(once) a: () -> (), @noescape(once) b: () -> ()) { /* snip */ }

func foo() {
  let a: Int
  let b: Int
  withNoEscape({ a = 4 }, { b = a + 2})
}

How does the compiler know that `a` has been assigned a value by the time that `b = a + 2` is executed? Nothing says that `withNoEscape` executes the two closures in "visual order”.

@Matthew: The @autoclosure was actually an accident; I meant @noescape. :) That said, yes, I think that we'll often want @noescape(once) on @autoclosure parameters.

As for "once?" seems to me that calling a non-escaping closure a second time on accident isn't a very frequent mistake, and when it happens it ought to be very easy to debug. It also doesn't add benefits outside of this verification, so it's unclear to me that the it pulls its own weight. Even as I was writing the @noescape(once) proposal, I was having doubts that it itself would pull its own weight.

@Brent: no matter how useful it is, we can't reasonably prove that an escaping closure is called exactly once without move semantics (and move-only closures). For now, if @once is distinct, it needs to be accompanied by @noescape.

@Gwendal: that's one solution. A third solution would be to enforce that @noescape(once) parameters are executed in parameter order.

Do you have any example of UIKit where a function accepts two closures that could reasonably be marked @noescape(once)?

Félix

···

Le 1 févr. 2016 à 09:31:53, Matthew Johnson <matthew@anandabits.com> a écrit :

func foo() {
  let bar: Int
  withNoEscape { bar = 1 }
}

func withNoEscape(@autoclosure(once) closure: () -> ()) { /* snip */ }

Looking back, I do think that there should be a way to exit from `withNoEscape` without calling the closure, so yes, throwing should imply that the closure wasn't executed. If it's possible that `foo` swallowed an error from a throwing `withNoEscape`, the compiler should assume that the variables within haven't been initialized:

I’m glad to see an @autoclosure func in this thread. We will want to be able to use this feature with @autoclosure in addition to @noescape.

As far as exiting without calling the closure, I suggest `@noescape(once?)`. The `?` indicates the closure may or may not be called, but will not be called more than once.

This would be handy in the case of the short-circuiting boolean operators, for example:

@warn_unused_result func &&<T : BooleanType, U : BooleanType>(_ lhs: T, @autoclosure(once?) _ rhs: () throws -> U) rethrows -> Bool

-Matthew

I don’t see how this is useful. You wouldn’t be able to initialize a value with this semantic, so it isn’t any more powerful than @noescape on the caller side.

-Chris

···

On Feb 1, 2016, at 6:31 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

func foo() {
  let bar: Int
  withNoEscape { bar = 1 }
}

func withNoEscape(@autoclosure(once) closure: () -> ()) { /* snip */ }

Looking back, I do think that there should be a way to exit from `withNoEscape` without calling the closure, so yes, throwing should imply that the closure wasn't executed. If it's possible that `foo` swallowed an error from a throwing `withNoEscape`, the compiler should assume that the variables within haven't been initialized:

I’m glad to see an @autoclosure func in this thread. We will want to be able to use this feature with @autoclosure in addition to @noescape.

As far as exiting without calling the closure, I suggest `@noescape(once?)`. The `?` indicates the closure may or may not be called, but will not be called more than once.

func foo() {
  let bar: Int
  withNoEscape { bar = 1 }
}

func withNoEscape(@autoclosure(once) closure: () -> ()) { /* snip */ }

Looking back, I do think that there should be a way to exit from `withNoEscape` without calling the closure, so yes, throwing should imply that the closure wasn't executed. If it's possible that `foo` swallowed an error from a throwing `withNoEscape`, the compiler should assume that the variables within haven't been initialized:

I’m glad to see an @autoclosure func in this thread. We will want to be able to use this feature with @autoclosure in addition to @noescape.

As far as exiting without calling the closure, I suggest `@noescape(once?)`. The `?` indicates the closure may or may not be called, but will not be called more than once.

I don’t see how this is useful. You wouldn’t be able to initialize a value with this semantic, so it isn’t any more powerful than @noescape on the caller side.

Right, you would not be able to use it for initialization.

It gives a guarantee that the closure will not be executed twice. This semantic guarantee could be useful, especially if the closure has side-effects. It adds the clarity of a guarantee to APIs where the zero-or-one-times semantic is implicit with @noescape alone.

-Matthew

···

On Feb 1, 2016, at 2:46 PM, Chris Lattner <clattner@apple.com> wrote:
On Feb 1, 2016, at 6:31 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

-Chris

No Félix, no example in UIKit itself. I was thinking of higher-order applications or library functions that wrap any stateful API. A very artificial example involving UIKit :

  func f(@noescape(once) closure1: () -> (), @noescape(once) closure2: () -> (), @noescape(once) closure3: () -> ()) {
    closure1()
    tableView.beginUpdates()
    closure2()
    tableView.endUpdates()
    closure3()
  }

So unless it would be too complex to implement, I would support letting the compiler accept several once closures, without requiring any execution order, and without double-guessing the needs of the developer.

Gwendal

···

Le 1 févr. 2016 à 18:29, Félix Cloutier via swift-evolution <swift-evolution@swift.org> a écrit :

@Gwendal: that's one solution. A third solution would be to enforce that @noescape(once) parameters are executed in parameter order.

Do you have any example of UIKit where a function accepts two closures that could reasonably be marked @noescape(once)?

Right, but unless the compiler is going to enforce it somehow, it doesn’t add any value above a comment. Particularly given that we want to keep the language simple where possible, I think that a comment would be perfectly fine for this. We don’t want to be in the business of providing a "documentation hook” in the language for every theoretical invariant someone might want to express.

-Chris

···

On Feb 1, 2016, at 1:02 PM, Matthew Johnson <matthew@anandabits.com> wrote:

I’m glad to see an @autoclosure func in this thread. We will want to be able to use this feature with @autoclosure in addition to @noescape.

As far as exiting without calling the closure, I suggest `@noescape(once?)`. The `?` indicates the closure may or may not be called, but will not be called more than once.

I don’t see how this is useful. You wouldn’t be able to initialize a value with this semantic, so it isn’t any more powerful than @noescape on the caller side.

Right, you would not be able to use it for initialization.

It gives a guarantee that the closure will not be executed twice. This semantic guarantee could be useful, especially if the closure has side-effects. It adds the clarity of a guarantee to APIs where the zero-or-one-times semantic is implicit with @noescape alone.

I’m glad to see an @autoclosure func in this thread. We will want to be able to use this feature with @autoclosure in addition to @noescape.

As far as exiting without calling the closure, I suggest `@noescape(once?)`. The `?` indicates the closure may or may not be called, but will not be called more than once.

I don’t see how this is useful. You wouldn’t be able to initialize a value with this semantic, so it isn’t any more powerful than @noescape on the caller side.

Right, you would not be able to use it for initialization.

It gives a guarantee that the closure will not be executed twice. This semantic guarantee could be useful, especially if the closure has side-effects. It adds the clarity of a guarantee to APIs where the zero-or-one-times semantic is implicit with @noescape alone.

Right, but unless the compiler is going to enforce it somehow, it doesn’t add any value above a comment. Particularly given that we want to keep the language simple where possible, I think that a comment would be perfectly fine for this. We don’t want to be in the business of providing a "documentation hook” in the language for every theoretical invariant someone might want to express.

I agree if the compiler isn’t going to enforce it. The value is in the guarantee.

Why wouldn’t the compiler enforce it in this case? Is it hard to implement? It doesn’t seem like it should be any harder than guaranteeing exactly once.

···

On Feb 1, 2016, at 3:25 PM, Chris Lattner <clattner@apple.com> wrote:
On Feb 1, 2016, at 1:02 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

-Chris

You’re right, the caller could enforce this, so it does provide marginal value beyond a comment. I think the rest of my point stands though.

-Chris

···

On Feb 1, 2016, at 1:43 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On Feb 1, 2016, at 3:25 PM, Chris Lattner <clattner@apple.com <mailto:clattner@apple.com>> wrote:

On Feb 1, 2016, at 1:02 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

I’m glad to see an @autoclosure func in this thread. We will want to be able to use this feature with @autoclosure in addition to @noescape.

As far as exiting without calling the closure, I suggest `@noescape(once?)`. The `?` indicates the closure may or may not be called, but will not be called more than once.

I don’t see how this is useful. You wouldn’t be able to initialize a value with this semantic, so it isn’t any more powerful than @noescape on the caller side.

Right, you would not be able to use it for initialization.

It gives a guarantee that the closure will not be executed twice. This semantic guarantee could be useful, especially if the closure has side-effects. It adds the clarity of a guarantee to APIs where the zero-or-one-times semantic is implicit with @noescape alone.

Right, but unless the compiler is going to enforce it somehow, it doesn’t add any value above a comment. Particularly given that we want to keep the language simple where possible, I think that a comment would be perfectly fine for this. We don’t want to be in the business of providing a "documentation hook” in the language for every theoretical invariant someone might want to express.

I agree if the compiler isn’t going to enforce it. The value is in the guarantee.

Why wouldn’t the compiler enforce it in this case? Is it hard to implement? It doesn’t seem like it should be any harder than guaranteeing exactly once.

I’m glad to see an @autoclosure func in this thread. We will want to be able to use this feature with @autoclosure in addition to @noescape.

As far as exiting without calling the closure, I suggest `@noescape(once?)`. The `?` indicates the closure may or may not be called, but will not be called more than once.

I don’t see how this is useful. You wouldn’t be able to initialize a value with this semantic, so it isn’t any more powerful than @noescape on the caller side.

Right, you would not be able to use it for initialization.

It gives a guarantee that the closure will not be executed twice. This semantic guarantee could be useful, especially if the closure has side-effects. It adds the clarity of a guarantee to APIs where the zero-or-one-times semantic is implicit with @noescape alone.

Right, but unless the compiler is going to enforce it somehow, it doesn’t add any value above a comment. Particularly given that we want to keep the language simple where possible, I think that a comment would be perfectly fine for this. We don’t want to be in the business of providing a "documentation hook” in the language for every theoretical invariant someone might want to express.

I agree if the compiler isn’t going to enforce it. The value is in the guarantee.

Why wouldn’t the compiler enforce it in this case? Is it hard to implement? It doesn’t seem like it should be any harder than guaranteeing exactly once.

You’re right, the caller could enforce this, so it does provide marginal value beyond a comment. I think the rest of my point stands though.

Sure, I think it’s a somewhat subjective judgement call.

I prefer to get as many guarantees as possible from the compiler. In cases where the semantics are likely to be documented in a comment, assumed or implicit I would prefer to see them expressed directly in the language.

I understand the case that it is simpler to leave it in a comment, I just have a different preference.

There have been other topics where a similar tradeoff / judgement call is relevant, such as the `local` access control proposal.

My sense so far is that Swift tries to strike a middle path, introducing annotations where the perceived benefit passes a test of being significant enough (such as the @noescape annotation and likely the once modifier). It seems like in *most* cases a mere semantic guarantee by the compiler isn’t considered to be benefit enough to pass that test - optimization potential and / or a new capability (such as the ability to initialize values).

-Matthew

···

On Feb 1, 2016, at 3:59 PM, Chris Lattner <clattner@apple.com> wrote:

On Feb 1, 2016, at 1:43 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Feb 1, 2016, at 3:25 PM, Chris Lattner <clattner@apple.com <mailto:clattner@apple.com>> wrote:
On Feb 1, 2016, at 1:02 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

-Chris

I updated the proposal to address some concerns. It can be found at: swift-evolution/00xx-noescape-once.md at master · fay59/swift-evolution · GitHub

Things that changed:

It now says that the closure must be called on code paths where the function throws;
you can have multiple @noescape(once) parameters but they can't make assumptions from one another.

I'm not 100% convinced that forcing a call on code paths that throw is always desirable. I've changed it because Chris's support probably means that the feature has better chances of making it, but I'm not convinced yet. If throwing allows me to return without calling the closure, I can write this:

do {
  let foo: Int
  try withLock(someLock, timeout: 0.5) {
    foo = sharedThing.foo
  }
} catch {
  print("couldn't acquire lock fast enough")
}

which would be kind of messy if instead, the closure needed a parameter to tell whether the lock was acquired or not when it runs.

Félix

I updated the proposal to address some concerns. It can be found at: https://github.com/zneak/swift-evolution/blob/master/proposals/00xx-noescape-once.md

Things that changed:

It now says that the closure must be called on code paths where the function throws;
you can have multiple @noescape(once) parameters but they can't make assumptions from one another.

I'm not 100% convinced that forcing a call on code paths that throw is always desirable. I've changed it because Chris's support probably means that the feature has better chances of making it, but I'm not convinced yet.

To be clear, (once) needs to specify either that the closure is guaranteed to be executed on the error path, or that it is guaranteed not to be. I can see the argument that “guaranteed not” is the best default. If you think that is the best way to go, feel free to make that be the proposal. Either way, please mention in “alternatives” that it needs to be one or the other, but could be switched (from whatever you propose) if there is a compelling reason.

-Chris

···

On Feb 3, 2016, at 1:21 PM, Félix Cloutier <felixcca@yahoo.ca> wrote:

If throwing allows me to return without calling the closure, I can write this:

do {
  let foo: Int
  try withLock(someLock, timeout: 0.5) {
    foo = sharedThing.foo
  }
} catch {
  print("couldn't acquire lock fast enough")
}

which would be kind of messy if instead, the closure needed a parameter to tell whether the lock was acquired or not when it runs.

Félix

Hello all,

I was wondering if this topic had evolved in anyway since its original introduction.

@noescape(once) would still be a useful addition to the language!

Gwendal Roué

···

Le 3 févr. 2016 à 22:21, Félix Cloutier via swift-evolution <swift-evolution@swift.org> a écrit :

I updated the proposal to address some concerns. It can be found at: https://github.com/zneak/swift-evolution/blob/master/proposals/00xx-noescape-once.md

Things that changed:

It now says that the closure must be called on code paths where the function throws;
you can have multiple @noescape(once) parameters but they can't make assumptions from one another.

I'm not 100% convinced that forcing a call on code paths that throw is always desirable. I've changed it because Chris's support probably means that the feature has better chances of making it, but I'm not convinced yet. If throwing allows me to return without calling the closure, I can write this:

do {
  let foo: Int
  try withLock(someLock, timeout: 0.5) {
    foo = sharedThing.foo
  }
} catch {
  print("couldn't acquire lock fast enough")
}

which would be kind of messy if instead, the closure needed a parameter to tell whether the lock was acquired or not when it runs.

Félix

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

Thanks. I have updated the proposal to reflect this.

One thing that remains unspecified is the behavior when the closure throws. Feedback and opinions would be very welcome here.

Some alternatives that I can think of:

disallow throwing from @noescape(once) closures;
allow throwing and allow the error to be re-thrown (and force `once` closures to be executed on throwing paths);
return the closure error (possibly incompatible with some function return types).

Félix

···

Le 3 févr. 2016 à 17:18:51, Chris Lattner <clattner@apple.com> a écrit :

On Feb 3, 2016, at 1:21 PM, Félix Cloutier <felixcca@yahoo.ca <mailto:felixcca@yahoo.ca>> wrote:

I updated the proposal to address some concerns. It can be found at: https://github.com/zneak/swift-evolution/blob/master/proposals/00xx-noescape-once.md

Things that changed:

It now says that the closure must be called on code paths where the function throws;
you can have multiple @noescape(once) parameters but they can't make assumptions from one another.

I'm not 100% convinced that forcing a call on code paths that throw is always desirable. I've changed it because Chris's support probably means that the feature has better chances of making it, but I'm not convinced yet.

To be clear, (once) needs to specify either that the closure is guaranteed to be executed on the error path, or that it is guaranteed not to be. I can see the argument that “guaranteed not” is the best default. If you think that is the best way to go, feel free to make that be the proposal. Either way, please mention in “alternatives” that it needs to be one or the other, but could be switched (from whatever you propose) if there is a compelling reason.

-Chris

If throwing allows me to return without calling the closure, I can write this:

do {
  let foo: Int
  try withLock(someLock, timeout: 0.5) {
    foo = sharedThing.foo
  }
} catch {
  print("couldn't acquire lock fast enough")
}

which would be kind of messy if instead, the closure needed a parameter to tell whether the lock was acquired or not when it runs.

Félix

Hi, not beyond this thread that I have seen. I think it's worth you
summarizing this thread in a formal proposal and putting it up for
discussion or submitting it as a PR :)

···

On Sunday, 10 April 2016, Gwendal Roué <swift-evolution@swift.org> wrote:

Hello all,

I was wondering if this topic had evolved in anyway since its original
introduction.

@noescape(once) would still be a useful addition to the language!

Gwendal Roué

Le 3 févr. 2016 à 22:21, Félix Cloutier via swift-evolution < > swift-evolution@swift.org > <javascript:_e(%7B%7D,'cvml','swift-evolution@swift.org');>> a écrit :

I updated the proposal to address some concerns. It can be found at:
https://github.com/zneak/swift-evolution/blob/master/proposals/00xx-noescape-once.md

Things that changed:

   - It now says that the closure must be called on code paths where the
   function throws;
   - you can have multiple @noescape(once) parameters but they can't make
   assumptions from one another.

I'm not 100% convinced that forcing a call on code paths that throw is
always desirable. I've changed it because Chris's support probably means
that the feature has better chances of making it, but I'm not convinced
yet. If throwing allows me to return without calling the closure, I can
write this:

do {
let foo: Int
try withLock(someLock, timeout: 0.5) {
foo = sharedThing.foo
}
} catch {
print("couldn't acquire lock fast enough")
}

which would be kind of messy if instead, the closure needed a parameter to
tell whether the lock was acquired or not when it runs.

Félix

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
<javascript:_e(%7B%7D,'cvml','swift-evolution@swift.org');>
https://lists.swift.org/mailman/listinfo/swift-evolution