Guaranteed closure execution

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;

I’d strongly prefer this. Once/if the base proposal is accepted and implemented, we can always expand from there if strong motivation exists for doing so.

-Chris

···

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

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 <mailto: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

Felix Cloutier already wrote one: https://github.com/zneak/swift-evolution/blob/master/proposals/00xx-noescape-once.md

Do you think it needs to be rewritten?

Gwendal Roué

···

Le 10 avr. 2016 à 14:56, Andrew Bennett <cacoyi@gmail.com> a écrit :

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 <mailto: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

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;

I’d strongly prefer this. Once/if the base proposal is accepted and implemented, we can always expand from there if strong motivation exists for doing so.

-Chris

Previously I had thought of the try? and try! but even with plain try the compiler might not know if a variable is initialized or not if the @noescape(once) must not run on throws

let foo: Int
do {
    try withLock(someLock, timeout: 0.5) {
        foo = sharedThing.foo
    }
    // immutable foo can be read not written to
} catch {
    print("couldn't acquire lock fast enough")
}
// is immutable foo initialized or not, must prevent both reads and writes

So will the compiler be able to properly handle this ambiguity?

Dany

···

Le 3 févr. 2016 à 19:22, Chris Lattner via swift-evolution <swift-evolution@swift.org> a écrit :

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

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 <mailto: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

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

Sorry I missed that scrolling back through the history, that proposal looks
great. It doesn't look like it has been submitted as a pull request to
swift-evolution yet though.

···

On Sunday, 10 April 2016, Gwendal Roué <gwendal.roue@gmail.com> wrote:

Felix Cloutier already wrote one:
https://github.com/zneak/swift-evolution/blob/master/proposals/00xx-noescape-once.md

Do you think it needs to be rewritten?

Gwendal Roué

Le 10 avr. 2016 à 14:56, Andrew Bennett <cacoyi@gmail.com > <javascript:_e(%7B%7D,'cvml','cacoyi@gmail.com');>> a écrit :

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 > <javascript:_e(%7B%7D,'cvml','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> 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

I believe so, but I can’t say with 100% certainty without diving into some more details.

-Chris

···

On Feb 3, 2016, at 4:45 PM, Dany St-Amant via swift-evolution <swift-evolution@swift.org> wrote:

Le 3 févr. 2016 à 19:22, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

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

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;

I’d strongly prefer this. Once/if the base proposal is accepted and implemented, we can always expand from there if strong motivation exists for doing so.

-Chris

Previously I had thought of the try? and try! but even with plain try the compiler might not know if a variable is initialized or not if the @noescape(once) must not run on throws

let foo: Int
do {
    try withLock(someLock, timeout: 0.5) {
        foo = sharedThing.foo
    }
    // immutable foo can be read not written to
} catch {
    print("couldn't acquire lock fast enough")
}
// is immutable foo initialized or not, must prevent both reads and writes

So will the compiler be able to properly handle this ambiguity?

It seems to me that it isn't very different from:

let foo: Int
if someCondition {
  foo = 4
}
// is immutable foo initialized or not, must prevent both reads and writes

which is currently handled properly.

Félix

···

Le 3 févr. 2016 à 19:45:11, Dany St-Amant via swift-evolution <swift-evolution@swift.org> a écrit :

Le 3 févr. 2016 à 19:22, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

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

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;

I’d strongly prefer this. Once/if the base proposal is accepted and implemented, we can always expand from there if strong motivation exists for doing so.

-Chris

Previously I had thought of the try? and try! but even with plain try the compiler might not know if a variable is initialized or not if the @noescape(once) must not run on throws

let foo: Int
do {
    try withLock(someLock, timeout: 0.5) {
        foo = sharedThing.foo
    }
    // immutable foo can be read not written to
} catch {
    print("couldn't acquire lock fast enough")
}
// is immutable foo initialized or not, must prevent both reads and writes

So will the compiler be able to properly handle this ambiguity?

Dany

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 <mailto: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

_______________________________________________
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

Hello Andrew,

I'm rather embarrassed: the initial design of this proposal was based on a modifier of @noescape:

  func f(@noescape(once) closure: () -> ()) { … }

But since the 0049 proposal has been accepted (https://github.com/apple/swift-evolution/blob/master/proposals/0049-noescape-autoclosure-type-attrs.md\), @noescape is no longer an argument qualifier, but a type attribute.

The `once` discussed here can not be part of the type: if noescape can understandably be part of the type, the fact that a function guarantees it will call a closure once is a quality of that function, not of the closure.

So the proposed @noescape(once) syntax is now confusing as it would mix a type attribute and a argument qualifier.

I don't quite know how to escape this:

  // two @ signs
  func f(@noescape @once closure: () -> ()) { … }

  // Implies @noescape
  func f(@once closure: () -> ()) { … }

I'd like advice from competent people before I would attempt a rewrite of the proposal.

Gwendal Roué

···

Le 10 avr. 2016 à 23:26, Andrew Bennett <cacoyi@gmail.com> a écrit :

Sorry I missed that scrolling back through the history, that proposal looks great. It doesn't look like it has been submitted as a pull request to swift-evolution yet though.

On Sunday, 10 April 2016, Gwendal Roué <gwendal.roue@gmail.com <mailto:gwendal.roue@gmail.com>> wrote:
Felix Cloutier already wrote one: https://github.com/zneak/swift-evolution/blob/master/proposals/00xx-noescape-once.md

Do you think it needs to be rewritten?

Gwendal Roué

Le 10 avr. 2016 à 14:56, Andrew Bennett <cacoyi@gmail.com <javascript:_e(%7B%7D,'cvml','cacoyi@gmail.com');>> a écrit :

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 <javascript:_e(%7B%7D,'cvml','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 <>> 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

Hi Gwendal,

Hello Andrew,

I'm rather embarrassed: the initial design of this proposal was based on a modifier of @noescape:

  func f(@noescape(once) closure: () -> ()) { … }

But since the 0049 proposal has been accepted (https://github.com/apple/swift-evolution/blob/master/proposals/0049-noescape-autoclosure-type-attrs.md\), @noescape is no longer an argument qualifier, but a type attribute.

maybe I cannot give competent advice from the language designers perspective, but I can speak as a language user.

I think it is more intuitive to have the name of the argument first: "ARGUMENT-NAME: NOESCAPE AUTOCLOSURE TYPE FOOBAR-QUALIFIER..., NEXT-ARGUMENT-NAME..." and not "SOME-MODIFIERS-FIRST ARGUMENT-NAME: SOME-MODIFIERS-LAST". I think the old behavior reminded me too much of C, where the argument name is at some arbitrary place, surrounded by type names, parens, and whatnot; "void foo(int __cdecl (*bar)(int foobar, void *context))" where "bar" is the argument name, the first "int" is the return type of the "bar" function, "int" and "void*" are the two argument types, and "__cdecl" is the calling convention of the "bar" function.

And about @noescape(once)... Would it not work anymore? I know it's not part of the current SE-0049 proposal, but I can't see why it couldn't be expanded to include `once`. Or maybe it's just because I can't think of a realistic use case for using "@noescape(once)".

The `once` discussed here can not be part of the type: if noescape can understandably be part of the type, the fact that a function guarantees it will call a closure once is a quality of that function, not of the closure.

Yes it is a quality of the function and not of the type. But why not just leave it at that place and just call it "argument qualifier"? Or maybe the general layout changed? In Swift 2 it was "ARGUMENT_QUALIFIERS ARGUMENT_NAME: TYPE_ATTRIBUTES TYPE", and in Swift 3 it is "ARGUMENT_NAME: ARGUMENT_QUALIFIERS TYPE_ATTRIBUTES TYPE"?

So the proposed @noescape(once) syntax is now confusing as it would mix a type attribute and a argument qualifier.

I don't quite know how to escape this:

  // two @ signs
  func f(@noescape @once closure: () -> ()) { … }

  // Implies @noescape
  func f(@once closure: () -> ()) { … }

How about

    func f(closure: @noescape(once) () -> ()) { ... }

-Michael

···

Am 23.04.2016 um 12:18 schrieb Gwendal Roué via swift-evolution <swift-evolution@swift.org>:

I'd like advice from competent people before I would attempt a rewrite of the proposal.

Gwendal Roué

Le 10 avr. 2016 à 23:26, Andrew Bennett <cacoyi@gmail.com> a écrit :

Sorry I missed that scrolling back through the history, that proposal looks great. It doesn't look like it has been submitted as a pull request to swift-evolution yet though.

On Sunday, 10 April 2016, Gwendal Roué <gwendal.roue@gmail.com> wrote:
Felix Cloutier already wrote one: https://github.com/zneak/swift-evolution/blob/master/proposals/00xx-noescape-once.md

Do you think it needs to be rewritten?

Gwendal Roué

Le 10 avr. 2016 à 14:56, Andrew Bennett <cacoyi@gmail.com> a écrit :

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> 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

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

I agree, it would be good if someone like Brent or Chris could chip in.
However, I think that @noescape(once) is still fine.

As long as a @noescape(once) closure must either be passed to a single
function, or called once it seems enforceable.

I don't believe a @noescape closure can be stored, so it doesn't change
anything.

···

On Sat, Apr 23, 2016 at 8:18 PM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

Hello Andrew,

I'm rather embarrassed: the initial design of this proposal was based on a
modifier of @noescape:

func f(@noescape(once) closure: () -> ()) { … }

But since the 0049 proposal has been accepted (
https://github.com/apple/swift-evolution/blob/master/proposals/0049-noescape-autoclosure-type-attrs.md\),
@noescape is no longer an argument qualifier, but a type attribute.

The `once` discussed here can not be part of the type: if noescape can
understandably be part of the type, the fact that a function guarantees it
will call a closure once is a quality of that function, not of the closure.

So the proposed @noescape(once) syntax is now confusing as it would mix a
type attribute and a argument qualifier.

I don't quite know how to escape this:

// two @ signs
func f(@noescape @once closure: () -> ()) { … }

// Implies @noescape
func f(@once closure: () -> ()) { … }

I'd like advice from competent people before I would attempt a rewrite of
the proposal.

Gwendal Roué

Le 10 avr. 2016 à 23:26, Andrew Bennett <cacoyi@gmail.com> a écrit :

Sorry I missed that scrolling back through the history, that proposal
looks great. It doesn't look like it has been submitted as a pull request
to swift-evolution yet though.

On Sunday, 10 April 2016, Gwendal Roué <gwendal.roue@gmail.com> wrote:

Felix Cloutier already wrote one:
https://github.com/zneak/swift-evolution/blob/master/proposals/00xx-noescape-once.md

Do you think it needs to be rewritten?

Gwendal Roué

Le 10 avr. 2016 à 14:56, Andrew Bennett <cacoyi@gmail.com> a écrit :

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> 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

Hello Andrew,

I'm rather embarrassed: the initial design of this proposal was based on a modifier of @noescape:

  func f(@noescape(once) closure: () -> ()) { … }

But since the 0049 proposal has been accepted (https://github.com/apple/swift-evolution/blob/master/proposals/0049-noescape-autoclosure-type-attrs.md\), @noescape is no longer an argument qualifier, but a type attribute.

The `once` discussed here can not be part of the type: if noescape can understandably be part of the type, the fact that a function guarantees it will call a closure once is a quality of that function, not of the closure.

So the proposed @noescape(once) syntax is now confusing as it would mix a type attribute and a argument qualifier.

I don't quite know how to escape this:

  // two @ signs
  func f(@noescape @once closure: () -> ()) { … }

  // Implies @noescape
  func f(@once closure: () -> ()) { … }

I'd like advice from competent people before I would attempt a rewrite of the proposal.

Hi Gwendal,

I don’t think that the movement of @noescape affects the approach: I’d suggest that a proposal (e.g. Felix’s) go with:

  func f(closure: @noescape(once) () -> ()) { … }

The semantics are clear: the closure is guaranteed to be called exactly once on all normal and “throw” paths. Paths that do not return in either of those ways (e.g. a call to abort) do not need to call the closure.

IMO, this is a small scope proposal that is likely to be accepted.

-Chris

···

On Apr 23, 2016, at 3:18 AM, Gwendal Roué via swift-evolution <swift-evolution@swift.org> wrote:

Gwendal Roué

Le 10 avr. 2016 à 23:26, Andrew Bennett <cacoyi@gmail.com <mailto:cacoyi@gmail.com>> a écrit :

Sorry I missed that scrolling back through the history, that proposal looks great. It doesn't look like it has been submitted as a pull request to swift-evolution yet though.

On Sunday, 10 April 2016, Gwendal Roué <gwendal.roue@gmail.com <mailto:gwendal.roue@gmail.com>> wrote:
Felix Cloutier already wrote one: https://github.com/zneak/swift-evolution/blob/master/proposals/00xx-noescape-once.md

Do you think it needs to be rewritten?

Gwendal Roué

Le 10 avr. 2016 à 14:56, Andrew Bennett <cacoyi@gmail.com <javascript:_e(%7B%7D,'cvml','cacoyi@gmail.com');>> a écrit :

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 <javascript:_e(%7B%7D,'cvml','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 <>> 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

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

Thanks for your input Michael,

You're right that argument modifiers should go before the argument, and type qualifiers before the type:

  func f(@once closure: @noescape () -> ())

Since @once actually implies @noescape (as described earlier in the thread), it can be shortened to:

  func f(@once closure: () -> ()) // implicit @noescape

Or maybe it's just because I can't think of a realistic use case for using "@noescape(once)".

The use case is the following: @once closures let the compiler know that the closure code is executed. It allows declaration of undefined `let` variables that are initialized in the @once closure:

  let x: Int
  f { x = 1 }
  // use x

Without @once, the developer has to work around:

  var x: Int = 0 // two problems: `var` declaration, and dummy value
  f { x = 1 }
  // use x

If this proposal would be accepted, the next step would be to ask for dispatch_sync functions et al. to use this qualifier (as well as other standard functions like autoreleasepool, withUnsafeBufferPointer, etc.):

  let x: Int
  dispatch_sync(queue) {
    x = 1
  }

And now high-level objects that use internal queues are easier to use:

  let items: [Item]
  let users: [User]
  dbQueue.inDatabase { db in
    items = Item.fetchAll(db)
    users = User.fetchAll(db)
  }

This pattern would greatly improve many libraries, and my own GitHub - groue/GRDB.swift: A toolkit for SQLite databases, with a focus on application development in particular.

Gwendal Roué

···

Le 23 avr. 2016 à 14:16, Michael Peternell <michael.peternell@gmx.at> a écrit :

Hi Gwendal,

Am 23.04.2016 um 12:18 schrieb Gwendal Roué via swift-evolution <swift-evolution@swift.org>:

Hello Andrew,

I'm rather embarrassed: the initial design of this proposal was based on a modifier of @noescape:

  func f(@noescape(once) closure: () -> ()) { … }

But since the 0049 proposal has been accepted (https://github.com/apple/swift-evolution/blob/master/proposals/0049-noescape-autoclosure-type-attrs.md\), @noescape is no longer an argument qualifier, but a type attribute.

maybe I cannot give competent advice from the language designers perspective, but I can speak as a language user.

I think it is more intuitive to have the name of the argument first: "ARGUMENT-NAME: NOESCAPE AUTOCLOSURE TYPE FOOBAR-QUALIFIER..., NEXT-ARGUMENT-NAME..." and not "SOME-MODIFIERS-FIRST ARGUMENT-NAME: SOME-MODIFIERS-LAST". I think the old behavior reminded me too much of C, where the argument name is at some arbitrary place, surrounded by type names, parens, and whatnot; "void foo(int __cdecl (*bar)(int foobar, void *context))" where "bar" is the argument name, the first "int" is the return type of the "bar" function, "int" and "void*" are the two argument types, and "__cdecl" is the calling convention of the "bar" function.

And about @noescape(once)... Would it not work anymore? I know it's not part of the current SE-0049 proposal, but I can't see why it couldn't be expanded to include `once`. Or maybe it's just because I can't think of a realistic use case for using "@noescape(once)".

The `once` discussed here can not be part of the type: if noescape can understandably be part of the type, the fact that a function guarantees it will call a closure once is a quality of that function, not of the closure.

Yes it is a quality of the function and not of the type. But why not just leave it at that place and just call it "argument qualifier"? Or maybe the general layout changed? In Swift 2 it was "ARGUMENT_QUALIFIERS ARGUMENT_NAME: TYPE_ATTRIBUTES TYPE", and in Swift 3 it is "ARGUMENT_NAME: ARGUMENT_QUALIFIERS TYPE_ATTRIBUTES TYPE"?

So the proposed @noescape(once) syntax is now confusing as it would mix a type attribute and a argument qualifier.

I don't quite know how to escape this:

  // two @ signs
  func f(@noescape @once closure: () -> ()) { … }

  // Implies @noescape
  func f(@once closure: () -> ()) { … }

How about

   func f(closure: @noescape(once) () -> ()) { ... }

-Michael

I'd like advice from competent people before I would attempt a rewrite of the proposal.

Gwendal Roué

Le 10 avr. 2016 à 23:26, Andrew Bennett <cacoyi@gmail.com> a écrit :

Sorry I missed that scrolling back through the history, that proposal looks great. It doesn't look like it has been submitted as a pull request to swift-evolution yet though.

On Sunday, 10 April 2016, Gwendal Roué <gwendal.roue@gmail.com> wrote:
Felix Cloutier already wrote one: https://github.com/zneak/swift-evolution/blob/master/proposals/00xx-noescape-once.md

Do you think it needs to be rewritten?

Gwendal Roué

Le 10 avr. 2016 à 14:56, Andrew Bennett <cacoyi@gmail.com> a écrit :

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> 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

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

Just curious, is there a deeper-than-semantic difference between a
@noescape(once) closure and an inlined closure?

···

On Sun, Apr 24, 2016, 5:58 PM Chris Lattner via swift-evolution < swift-evolution@swift.org> wrote:

On Apr 23, 2016, at 3:18 AM, Gwendal Roué via swift-evolution < > swift-evolution@swift.org> wrote:

Hello Andrew,

I'm rather embarrassed: the initial design of this proposal was based on a
modifier of @noescape:

func f(@noescape(once) closure: () -> ()) { … }

But since the 0049 proposal has been accepted (
https://github.com/apple/swift-evolution/blob/master/proposals/0049-noescape-autoclosure-type-attrs.md\),
@noescape is no longer an argument qualifier, but a type attribute.

The `once` discussed here can not be part of the type: if noescape can
understandably be part of the type, the fact that a function guarantees it
will call a closure once is a quality of that function, not of the closure.

So the proposed @noescape(once) syntax is now confusing as it would mix a
type attribute and a argument qualifier.

I don't quite know how to escape this:

// two @ signs
func f(@noescape @once closure: () -> ()) { … }

// Implies @noescape
func f(@once closure: () -> ()) { … }

I'd like advice from competent people before I would attempt a rewrite of
the proposal.

Hi Gwendal,

I don’t think that the movement of @noescape affects the approach: I’d
suggest that a proposal (e.g. Felix’s) go with:

func f(closure: @noescape(once) () -> ()) { … }

The semantics are clear: the closure is guaranteed to be called exactly
once on all normal and “throw” paths. Paths that do not return in either
of those ways (e.g. a call to abort) do not need to call the closure.

IMO, this is a small scope proposal that is likely to be accepted.

-Chris

Gwendal Roué

Le 10 avr. 2016 à 23:26, Andrew Bennett <cacoyi@gmail.com> a écrit :

Sorry I missed that scrolling back through the history, that proposal
looks great. It doesn't look like it has been submitted as a pull request
to swift-evolution yet though.

On Sunday, 10 April 2016, Gwendal Roué <gwendal.roue@gmail.com> wrote:

Felix Cloutier already wrote one:
https://github.com/zneak/swift-evolution/blob/master/proposals/00xx-noescape-once.md

Do you think it needs to be rewritten?

Gwendal Roué

Le 10 avr. 2016 à 14:56, Andrew Bennett <cacoyi@gmail.com> a écrit :

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> 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

_______________________________________________
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

Hi Gwendal,

I don’t think that the movement of @noescape affects the approach: I’d suggest that a proposal (e.g. Felix’s) go with:

  func f(closure: @noescape(once) () -> ()) { … }

The semantics are clear: the closure is guaranteed to be called exactly once on all normal and “throw” paths. Paths that do not return in either of those ways (e.g. a call to abort) do not need to call the closure.

IMO, this is a small scope proposal that is likely to be accepted.

The pull request is open: Proposal for a @noescape(once) modifier by groue · Pull Request #271 · apple/swift-evolution · GitHub

Gwendal Roué

[...]

Since @once actually implies @noescape (as described earlier in the thread), it can be shortened to:

[...]

I'm surprised that @once would imply @noescape. In my opinion this makes it much less useful. For example, it is common to have asynchronous operations with a completion handler -- the completion handler will definitely escape and should definitely get called once.

In some cases the completion handler may get passed to other functions that take an @once block, so that should count as passing off your responsibility to call it.

In other cases, it may get stored in an object representing a long-lived async operation. This is trickier, but I would hope that a nullable var property, only in a reference type, could be marked @once. Reading that property would return an @once block *and* clear the property (so you'd be back to needing to call it or pass it away). It is unclear if only closure properties should be allowed to be marked @once, but presumable the implementation cloud support any type.

A simple noescape @once might find a few trivial uses in my codebase, but the really hard to diagnose bugs have been in async code where escaping is natural and necessary.

-tim

···

On Apr 23, 2016, at 5:56 AM, Gwendal Roué via swift-evolution <swift-evolution@swift.org>

Hi,

Thanks for your input Michael,

You're right that argument modifiers should go before the argument, and type qualifiers before the type:

  func f(@once closure: @noescape () -> ())

Since @once actually implies @noescape (as described earlier in the thread), it can be shortened to:

  func f(@once closure: () -> ()) // implicit @noescape

why not just write

    func f(closure: @once () -> ()) // ?

Because, to be honest, I don't want to have to learn the difference between an "argument qualifier" and a "type attribute". They are both just words to me. Internally, I have two conflicting definitions of them: 1) "argument qualifier" = "what is left of the name", "type attribute" = "what comes right after the name, but before the type" and 2) don't wanna say it because it's even more wrong. What I wanna say: the average user will not care about that distinction, he will not want to understand it, and he certainly will not want to have to understand it. And putting everything (however it is called), after the name, looks much nicer IMHO. I also think that this is one of the reason why SE-0049 was accepted. Furthermore, I don't think my definition 1) is too wrong, because I think the implementation of SE-0049 is a syntax-only change, right?

Or maybe it's just because I can't think of a realistic use case for using "@noescape(once)".

The use case is the following: @once closures let the compiler know that the closure code is executed. It allows declaration of undefined `let` variables that are initialized in the @once closure:

  let x: Int
  f { x = 1 }
  // use x

Without @once, the developer has to work around:

  var x: Int = 0 // two problems: `var` declaration, and dummy value
  f { x = 1 }
  // use x

If this proposal would be accepted, the next step would be to ask for dispatch_sync functions et al. to use this qualifier (as well as other standard functions like autoreleasepool, withUnsafeBufferPointer, etc.):

  let x: Int
  dispatch_sync(queue) {
    x = 1
  }

And now high-level objects that use internal queues are easier to use:

  let items: [Item]
  let users: [User]
  dbQueue.inDatabase { db in
    items = Item.fetchAll(db)
    users = User.fetchAll(db)
  }

These are all nice examples. If @once can be combined with the let/var-initialization system, it's a real improvement. I think dispatch_sync should be defined like

    func dispatch_sync(queue: dispatch_queue_t, block: @once () throws -> ()) rethrows { ... }

and since @once implies @noescape, @noescape would be optional. The following would be equivalent:

    func dispatch_sync(queue: dispatch_queue_t, block: @once @noescape () throws -> ()) rethrows { ... }

I think that our opinions only differ regarding the proper syntax of these features...

Another feature I would like to have is converting a block into a noescape block. This seems necessary when creating wrappers around existing APIs. E.g.

    func swift_dispatch_sync(queue: dispatch_queue_t, block: @noescape! () -> ()) {
        dispatch_sync(queue, block: block)
    }

(note the "!" after "@noescape".)
This should behave like this:

    func swift_dispatch_sync(queue: dispatch_queue_t, block: @noescape () -> ()) {
        var isAlive = true
        let noescapeBlock = {
            if(!isAlive) { fatalError("noescape block called out of scope") }
            block()
        }
        defer { isAlive = false }
        dispatch_sync(queue, block: noescapeBlock)
    }

I would like to tell the compiler: "I see that you cannot prove at compile time, that this closure doesn't escape, but believe me when I say, 'it really really does not escape'. Can you please be less skeptical? May the fatalError hit me if I'm wrong." (And the same would apply to '@once', or '@noescape(once)'.)

When I think of it, my experimental synchronized-function would also profit from '@once':

    /// same as Objective-C's @synchronized(object) { ... } function.
    func synchronized<T>(lock: AnyObject, @noescape _ closure: () throws -> T) rethrows -> T {
        var result: T;
        objc_sync_enter(lock)
        defer { objc_sync_exit(lock) }
        result = try closure()
        return result;
    }

-Michael

···

Am 23.04.2016 um 14:56 schrieb Gwendal Roué via swift-evolution <swift-evolution@swift.org>:

This pattern would greatly improve many libraries, and my own GitHub - groue/GRDB.swift: A toolkit for SQLite databases, with a focus on application development in particular.

Gwendal Roué

Le 23 avr. 2016 à 14:16, Michael Peternell <michael.peternell@gmx.at> a écrit :

Hi Gwendal,

Am 23.04.2016 um 12:18 schrieb Gwendal Roué via swift-evolution <swift-evolution@swift.org>:

Hello Andrew,

I'm rather embarrassed: the initial design of this proposal was based on a modifier of @noescape:

  func f(@noescape(once) closure: () -> ()) { … }

But since the 0049 proposal has been accepted (https://github.com/apple/swift-evolution/blob/master/proposals/0049-noescape-autoclosure-type-attrs.md\), @noescape is no longer an argument qualifier, but a type attribute.

maybe I cannot give competent advice from the language designers perspective, but I can speak as a language user.

I think it is more intuitive to have the name of the argument first: "ARGUMENT-NAME: NOESCAPE AUTOCLOSURE TYPE FOOBAR-QUALIFIER..., NEXT-ARGUMENT-NAME..." and not "SOME-MODIFIERS-FIRST ARGUMENT-NAME: SOME-MODIFIERS-LAST". I think the old behavior reminded me too much of C, where the argument name is at some arbitrary place, surrounded by type names, parens, and whatnot; "void foo(int __cdecl (*bar)(int foobar, void *context))" where "bar" is the argument name, the first "int" is the return type of the "bar" function, "int" and "void*" are the two argument types, and "__cdecl" is the calling convention of the "bar" function.

And about @noescape(once)... Would it not work anymore? I know it's not part of the current SE-0049 proposal, but I can't see why it couldn't be expanded to include `once`. Or maybe it's just because I can't think of a realistic use case for using "@noescape(once)".

The `once` discussed here can not be part of the type: if noescape can understandably be part of the type, the fact that a function guarantees it will call a closure once is a quality of that function, not of the closure.

Yes it is a quality of the function and not of the type. But why not just leave it at that place and just call it "argument qualifier"? Or maybe the general layout changed? In Swift 2 it was "ARGUMENT_QUALIFIERS ARGUMENT_NAME: TYPE_ATTRIBUTES TYPE", and in Swift 3 it is "ARGUMENT_NAME: ARGUMENT_QUALIFIERS TYPE_ATTRIBUTES TYPE"?

So the proposed @noescape(once) syntax is now confusing as it would mix a type attribute and a argument qualifier.

I don't quite know how to escape this:

  // two @ signs
  func f(@noescape @once closure: () -> ()) { … }

  // Implies @noescape
  func f(@once closure: () -> ()) { … }

How about

  func f(closure: @noescape(once) () -> ()) { ... }

-Michael

I'd like advice from competent people before I would attempt a rewrite of the proposal.

Gwendal Roué

Le 10 avr. 2016 à 23:26, Andrew Bennett <cacoyi@gmail.com> a écrit :

Sorry I missed that scrolling back through the history, that proposal looks great. It doesn't look like it has been submitted as a pull request to swift-evolution yet though.

On Sunday, 10 April 2016, Gwendal Roué <gwendal.roue@gmail.com> wrote:
Felix Cloutier already wrote one: https://github.com/zneak/swift-evolution/blob/master/proposals/00xx-noescape-once.md

Do you think it needs to be rewritten?

Gwendal Roué

Le 10 avr. 2016 à 14:56, Andrew Bennett <cacoyi@gmail.com> a écrit :

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> 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

Oh, and I forgot to mention that this would mean deinit would need to extract the block it it hadn't been done already. I would hope (to avoid silent errors) that the compiler would require a reference type with an @once property to have a deinit that checks for a lingering value and deals with it. This would have avoided a few bugs over the years...

-tim

···

On Apr 23, 2016, at 9:33 AM, Timothy Wood via swift-evolution <swift-evolution@swift.org> wrote:

On Apr 23, 2016, at 5:56 AM, Gwendal Roué via swift-evolution <swift-evolution@swift.org>

[...]

Since @once actually implies @noescape (as described earlier in the thread), it can be shortened to:

[...]

I'm surprised that @once would imply @noescape. In my opinion this makes it much less useful. For example, it is common to have asynchronous operations with a completion handler -- the completion handler will definitely escape and should definitely get called once.

In some cases the completion handler may get passed to other functions that take an @once block, so that should count as passing off your responsibility to call it.

In other cases, it may get stored in an object representing a long-lived async operation. This is trickier, but I would hope that a nullable var property, only in a reference type, could be marked @once. Reading that property would return an @once block *and* clear the property (so you'd be back to needing to call it or pass it away). It is unclear if only closure properties should be allowed to be marked @once, but presumable the implementation cloud support any type.

A simple noescape @once might find a few trivial uses in my codebase, but the really hard to diagnose bugs have been in async code where escaping is natural and necessary.

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

why not just write

   func f(closure: @once () -> ()) // ?

Because, to be honest, I don't want to have to learn the difference between an "argument qualifier" and a "type attribute".

I'm with you here, but I will certainly not fight a Swift lawyer about the actual syntax :-)

For me the best syntax is the following because it makes the @noescape explicit, so that I know that I don't need to use explicit `self.` inside the closure.

  func f(closure: @noescape(once) () -> ())

  let x: Int
  f { x = myIntProperty } // implicit self

When I think of it, my experimental synchronized-function would also profit from '@once':

   /// same as Objective-C's @synchronized(object) { ... } function.
   func synchronized<T>(lock: AnyObject, @noescape _ closure: () throws -> T) rethrows -> T {
       var result: T;
       objc_sync_enter(lock)
       defer { objc_sync_exit(lock) }
       result = try closure()
       return result;
   }

I'm glad you see the point :-) I really believe that @noescape(once) is on the same boat as SE-0061 "Add Generic Result and Error Handling to autoreleasepool() https://github.com/apple/swift-evolution/blob/master/proposals/0061-autoreleasepool-signature.md

Gwendal

···

Le 23 avr. 2016 à 15:47, michael.peternell@gmx.at a écrit :

[...]

Since @once actually implies @noescape (as described earlier in the thread), it can be shortened to:

[...]

I'm surprised that @once would imply @noescape. In my opinion this makes it much less useful. For example, it is common to have asynchronous operations with a completion handler -- the completion handler will definitely escape and should definitely get called once.

I'm sorry but the goal of this proposal is not abstract, but very very very concrete:

  let x: Int
  f { x = 1 }
  // use x without compiler error

If you break this (and you do), you miss the point entirely.

In some cases the completion handler may get passed to other functions that take an @once block, so that should count as passing off your responsibility to call it.

In other cases, it may get stored in an object representing a long-lived async operation. This is trickier, but I would hope that a nullable var property, only in a reference type, could be marked @once. Reading that property would return an @once block *and* clear the property (so you'd be back to needing to call it or pass it away). It is unclear if only closure properties should be allowed to be marked @once, but presumable the implementation cloud support any type.

A simple noescape @once might find a few trivial uses in my codebase, but the really hard to diagnose bugs have been in async code where escaping is natural and necessary.

You are describing another proposal. Please please please please don't pollute this one.

Gwendal Roué

···

Le 23 avr. 2016 à 18:33, Timothy Wood <tjw@me.com> a écrit :

On Apr 23, 2016, at 5:56 AM, Gwendal Roué via swift-evolution <swift-evolution@swift.org>

I should try to rewrite my last answer, which did not explain why @once implies @noescape.

It is because @once is not a documenting token that is ignored by the compiler. Its goal is to be actually *used* by the compiler - and let the following snippet compile:

  func f(closure: @noescape(once) () -> ()) { closure() }
  
  let x: Int
  f { x = 1 }
  // use x without compiler complaining that x may be uninitialized.

For this pattern to work, the compiler needs to be able to prove that the closure has been called once at the end of the f function execution.

If the @once would only mean "will eventually be called once", the compiler would have the biggest difficulties proving it (one can not prove for example that NSOperation's completionBlock is called. It is a documentation given, not a provable fact), and the compiler would not accept the use of a variable initialized in a `once` closure, as in the above sample code.

That's why this proposal focuses on a `once` modifier that implies @noescape, AND allows the use of variables initialized in such a closure.

I said in an earlier post that this was a natural extension of SE-0061 "Add Generic Result and Error Handling to autoreleasepool() https://github.com/apple/swift-evolution/blob/master/proposals/0061-autoreleasepool-signature.md

Before SE-0061, we had to write:

  var x: Int = 0
  autoreleasepol {
    x = 1
  }
  // use x

With SE-0061, we can write:

  let x = autoreleasepol {
    return 1
  }
  // use x

With this proposal, we can write:

  let x: Int
  let y: String
  autoreleasepol {
    x = 1
    y = "foo"
  }
  // use x and y

Gwendal Roué

···

Le 23 avr. 2016 à 18:33, Timothy Wood <tjw@me.com> a écrit :

On Apr 23, 2016, at 5:56 AM, Gwendal Roué via swift-evolution <swift-evolution@swift.org>

[...]

Since @once actually implies @noescape (as described earlier in the thread), it can be shortened to:

[...]

I'm surprised that @once would imply @noescape.

Bumping this back up as it's a huge win for safety.

Many async functions use completion handlers as a sort of return, but there is no compiler enforcement that the passed-in closure is called in all flow branches of a method. I recently went through a maddening exercise tracking down a bug in an app, and found that in so many places the completion handler was not called, particularly when it's not the happy path.