Pitch: @required attribute for closures


(Charles Srstka) #1

MOTIVATION:

As per the current situation, there is a pitfall when writing asynchronous APIs that does not occur when writing synchronous APIs. Consider the following synchronous API:

func doSomething() -> SomeEnum {
  if aCondition {
    if bCondition {
      return .Foo
    } else {
      return .Bar
    }
  } else {
    if cCondition {
      return .Baz
    }
  }
}

The compiler will give an error here, since if both aCondition and cCondition are false, the function will not return anything.

However, consider the equivalent async API:

func doSomething(completionHandler: (SomeEnum) -> ()) {
  dispatch_async(someQueue) {
    if aCondition {
      if bCondition {
        completionHandler(.Foo)
      } else {
        completionHandler(.Bar)
      }
    } else {
      if cCondition {
        completionHandler(.Baz)
      }
    }
  }
}

Whoops, now the function can return without ever firing its completion handler, and the problem might not be discovered until runtime (and, depending on the complexity of the function, may be hard to find).

PROPOSED SOLUTION:

Add a @required attribute that can be applied to closure arguments. This attribute simply states that the given closure will always be eventually called, and the compiler can enforce this.

DETAILED DESIGN:

- The @required attribute states in our API contract that a given closure *must* be called at some point after the function is called.

- Standard API calls like dispatch_async that contractually promise to execute a closure or block get @required added to their signatures.

- When the compiler sees a @required closure in a function declaration, it checks to make sure that every execution path either calls the closure at some point, or sends a @required closure to another API that eventually ends up calling the closure.

- If there’s a way for a @required closure not to be called, the compiler emits an error letting the developer know about the bug in his/her code.

IMPACT ON EXISTING CODE:

None. This is purely additive.

ALTERNATIVES CONSIDERED:

I got nothin’.

Charles


(Patrick Pijnappel) #2

This has actually been proposed before, see SE-0073:
https://github.com/apple/swift-evolution/blob/master/proposals/0073-noescape-once.md

···

On Sun, Jun 5, 2016 at 11:37 AM, Charles Srstka via swift-evolution < swift-evolution@swift.org> wrote:

MOTIVATION:

As per the current situation, there is a pitfall when writing asynchronous
APIs that does not occur when writing synchronous APIs. Consider the
following synchronous API:

func doSomething() -> SomeEnum {
        if aCondition {
                if bCondition {
                        return .Foo
                } else {
                        return .Bar
                }
        } else {
                if cCondition {
                        return .Baz
                }
        }
}

The compiler will give an error here, since if both aCondition and
cCondition are false, the function will not return anything.

However, consider the equivalent async API:

func doSomething(completionHandler: (SomeEnum) -> ()) {
        dispatch_async(someQueue) {
                if aCondition {
                        if bCondition {
                                completionHandler(.Foo)
                        } else {
                                completionHandler(.Bar)
                        }
                } else {
                        if cCondition {
                                completionHandler(.Baz)
                        }
                }
        }
}

Whoops, now the function can return without ever firing its completion
handler, and the problem might not be discovered until runtime (and,
depending on the complexity of the function, may be hard to find).

PROPOSED SOLUTION:

Add a @required attribute that can be applied to closure arguments. This
attribute simply states that the given closure will always be eventually
called, and the compiler can enforce this.

DETAILED DESIGN:

- The @required attribute states in our API contract that a given closure
*must* be called at some point after the function is called.

- Standard API calls like dispatch_async that contractually promise to
execute a closure or block get @required added to their signatures.

- When the compiler sees a @required closure in a function declaration, it
checks to make sure that every execution path either calls the closure at
some point, or sends a @required closure to another API that eventually
ends up calling the closure.

- If there’s a way for a @required closure not to be called, the compiler
emits an error letting the developer know about the bug in his/her code.

IMPACT ON EXISTING CODE:

None. This is purely additive.

ALTERNATIVES CONSIDERED:

I got nothin’.

Charles

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


(Michael Peternell) #3

MOTIVATION:

As per the current situation, there is a pitfall when writing asynchronous APIs that does not occur when writing synchronous APIs. Consider the following synchronous API:

func doSomething() -> SomeEnum {
  if aCondition {
    if bCondition {
      return .Foo
    } else {
      return .Bar
    }
  } else {
    if cCondition {
      return .Baz
    }
  }
}

That's a good design...

The compiler will give an error here, since if both aCondition and cCondition are false, the function will not return anything.

However, consider the equivalent async API:

func doSomething(completionHandler: (SomeEnum) -> ()) {
  dispatch_async(someQueue) {
    if aCondition {
      if bCondition {
        completionHandler(.Foo)
      } else {
        completionHandler(.Bar)
      }
    } else {
      if cCondition {
        completionHandler(.Baz)
      }
    }
  }
}

This is not a good design. If you want the completion handler to be called, you can rewrite the function to make this intent obvious:

func doSomethingAsync(completionHandler: (SomeEnum) -> ()) {
    dispatch_async(someQueue) {
        let result = doSomething()
        completionBlock(result)
    }
}

func doSomething() -> SomeEnum {
    if aCondition {
        if bCondition {
            return .Foo
        } else {
            return .Bar
        }
    } else {
        if cCondition {
            return .Baz
        }
    }
}

and this begs the question why the doSomethingAsync() function is needed at all.. Calling dispatch_async directly is probably more convenient and the abstraction of doSomething() probably not that useful.. (I know the example is overly simplistic, but I can apply the same reasoning to more complex scenarios just as easily too.)

IMHO, asynchronous programming is only a good thing when used in moderation. In most cases that I see, it just makes everything harder to reason about. For most applications, it's best to have one UI thread and one worker thread. This leads to code that is debuggable, testable and inspectable. For example, it wouldn't help a C compiler if it can use multiple threads to compile a file; usually you have much more files to compile than cores, so you can let `make` do the scheduling. Concurrency-management code and application code should never be mixed, as much as possible and practical. Asynchronous code violates this principle regularly.

assert(async programming == Goto 2.0)

-Michael

···

Am 05.06.2016 um 11:37 schrieb Charles Srstka via swift-evolution <swift-evolution@swift.org>:


(Matthew Johnson) #4

This has actually been proposed before, see SE-0073: https://github.com/apple/swift-evolution/blob/master/proposals/0073-noescape-once.md

Actually that proposal was for noescape closures and this suggestion is for escaping closures. I don't think the compiler can verify this for noescape closures. If it is possible it would be far more complicated.

···

Sent from my iPad

On Jun 5, 2016, at 5:02 AM, Patrick Pijnappel via swift-evolution <swift-evolution@swift.org> wrote:

On Sun, Jun 5, 2016 at 11:37 AM, Charles Srstka via swift-evolution <swift-evolution@swift.org> wrote:
MOTIVATION:

As per the current situation, there is a pitfall when writing asynchronous APIs that does not occur when writing synchronous APIs. Consider the following synchronous API:

func doSomething() -> SomeEnum {
        if aCondition {
                if bCondition {
                        return .Foo
                } else {
                        return .Bar
                }
        } else {
                if cCondition {
                        return .Baz
                }
        }
}

The compiler will give an error here, since if both aCondition and cCondition are false, the function will not return anything.

However, consider the equivalent async API:

func doSomething(completionHandler: (SomeEnum) -> ()) {
        dispatch_async(someQueue) {
                if aCondition {
                        if bCondition {
                                completionHandler(.Foo)
                        } else {
                                completionHandler(.Bar)
                        }
                } else {
                        if cCondition {
                                completionHandler(.Baz)
                        }
                }
        }
}

Whoops, now the function can return without ever firing its completion handler, and the problem might not be discovered until runtime (and, depending on the complexity of the function, may be hard to find).

PROPOSED SOLUTION:

Add a @required attribute that can be applied to closure arguments. This attribute simply states that the given closure will always be eventually called, and the compiler can enforce this.

DETAILED DESIGN:

- The @required attribute states in our API contract that a given closure *must* be called at some point after the function is called.

- Standard API calls like dispatch_async that contractually promise to execute a closure or block get @required added to their signatures.

- When the compiler sees a @required closure in a function declaration, it checks to make sure that every execution path either calls the closure at some point, or sends a @required closure to another API that eventually ends up calling the closure.

- If there’s a way for a @required closure not to be called, the compiler emits an error letting the developer know about the bug in his/her code.

IMPACT ON EXISTING CODE:

None. This is purely additive.

ALTERNATIVES CONSIDERED:

I got nothin’.

Charles

_______________________________________________
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


(Charles Srstka) #5

Well, the example I gave *was* deliberately simplistic; there are many cases where the code needs to call APIs which themselves are asynchronous. If the purpose of doSomething() is to grab something from the network, and then do something with it, then it’s going to be using async APIs under the hood if you’re doing it the recommended way. Similarly, if the method grabs something from another task using XPC, that’s asynchronous. Pop up a sheet to ask the user for feedback before continuing? Asynchronous. NSUserScriptTask? Asynchronous. Yeah, you can use a dispatch semaphore to hack these async APIs into synchronous ones, but then you’re violating Apple’s recommendation about never blocking dispatch queues.

Anyway, I don’t think asynchronous APIs are going away any time soon, and it would be nice if it were easier to safely write them.

Charles

···

On Jun 5, 2016, at 10:28 AM, Michael Peternell <michael.peternell@gmx.at> wrote:

This is not a good design. If you want the completion handler to be called, you can rewrite the function to make this intent obvious:

func doSomethingAsync(completionHandler: (SomeEnum) -> ()) {
   dispatch_async(someQueue) {
       let result = doSomething()
       completionBlock(result)
   }
}

func doSomething() -> SomeEnum {
   if aCondition {
       if bCondition {
           return .Foo
       } else {
           return .Bar
       }
   } else {
       if cCondition {
           return .Baz
       }
   }
}

and this begs the question why the doSomethingAsync() function is needed at all.. Calling dispatch_async directly is probably more convenient and the abstraction of doSomething() probably not that useful.. (I know the example is overly simplistic, but I can apply the same reasoning to more complex scenarios just as easily too.)

IMHO, asynchronous programming is only a good thing when used in moderation. In most cases that I see, it just makes everything harder to reason about. For most applications, it's best to have one UI thread and one worker thread. This leads to code that is debuggable, testable and inspectable. For example, it wouldn't help a C compiler if it can use multiple threads to compile a file; usually you have much more files to compile than cores, so you can let `make` do the scheduling. Concurrency-management code and application code should never be mixed, as much as possible and practical. Asynchronous code violates this principle regularly.

assert(async programming == Goto 2.0)


(Charlie Monroe) #6

While I agree with Michael that nowadays, a lot of stuff that doesn't need to be, is done async, which leads to a giant thread pool per app and developers nowadays do not think of the cost of inter-thread communication (i.e. each dispatch_(a)sync has its cost, even though it's a light-weight thread), I agree with Charles that something like suggested does indeed help debugging issues with multi-thread apps.

···

On Jun 5, 2016, at 7:59 PM, Charles Srstka via swift-evolution <swift-evolution@swift.org> wrote:

On Jun 5, 2016, at 10:28 AM, Michael Peternell <michael.peternell@gmx.at <mailto:michael.peternell@gmx.at>> wrote:

This is not a good design. If you want the completion handler to be called, you can rewrite the function to make this intent obvious:

func doSomethingAsync(completionHandler: (SomeEnum) -> ()) {
   dispatch_async(someQueue) {
       let result = doSomething()
       completionBlock(result)
   }
}

func doSomething() -> SomeEnum {
   if aCondition {
       if bCondition {
           return .Foo
       } else {
           return .Bar
       }
   } else {
       if cCondition {
           return .Baz
       }
   }
}

and this begs the question why the doSomethingAsync() function is needed at all.. Calling dispatch_async directly is probably more convenient and the abstraction of doSomething() probably not that useful.. (I know the example is overly simplistic, but I can apply the same reasoning to more complex scenarios just as easily too.)

IMHO, asynchronous programming is only a good thing when used in moderation. In most cases that I see, it just makes everything harder to reason about. For most applications, it's best to have one UI thread and one worker thread. This leads to code that is debuggable, testable and inspectable. For example, it wouldn't help a C compiler if it can use multiple threads to compile a file; usually you have much more files to compile than cores, so you can let `make` do the scheduling. Concurrency-management code and application code should never be mixed, as much as possible and practical. Asynchronous code violates this principle regularly.

assert(async programming == Goto 2.0)

Well, the example I gave *was* deliberately simplistic; there are many cases where the code needs to call APIs which themselves are asynchronous. If the purpose of doSomething() is to grab something from the network, and then do something with it, then it’s going to be using async APIs under the hood if you’re doing it the recommended way. Similarly, if the method grabs something from another task using XPC, that’s asynchronous. Pop up a sheet to ask the user for feedback before continuing? Asynchronous. NSUserScriptTask? Asynchronous. Yeah, you can use a dispatch semaphore to hack these async APIs into synchronous ones, but then you’re violating Apple’s recommendation about never blocking dispatch queues.

Anyway, I don’t think asynchronous APIs are going away any time soon, and it would be nice if it were easier to safely write them.

Charles

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


(Andrew Bennett) #7

I like this.

One of the suggestions on @noescape(once) was that it just becomes @once
and works with escaping closures too. It might be possible if compile time
checks verified that the closure isn't copied, and that it is called before
being deinit-ialized. Failing that I'm happy with a runtime circumstance in
the cases the compiler can't check.

It would be great if @required took into the account the feedback from that
proposal and considered the synchronous case too.

As an aside, you can get some of the guarantees you want like this:

func doSomething(completionHandler: (SomeEnum) -> ()) {

  dispatch_async(someQueue) {

    let result: SomeEnum

    // the compiler ensures 'result' is set

    defer { completionHandler(result) }

    if aCondition {

      if bCondition {

        result = .Foo

      } else {

        result = .Bar

      }

      // the compiler ensures you do this, because it is 'let'

      return
    }

    if cCondition {

      result = .Baz

    }

  }

}

···

On Sun, Jun 5, 2016 at 9:42 PM, Matthew Johnson via swift-evolution < swift-evolution@swift.org> wrote:

Sent from my iPad

On Jun 5, 2016, at 5:02 AM, Patrick Pijnappel via swift-evolution < > swift-evolution@swift.org> wrote:

This has actually been proposed before, see SE-0073:
https://github.com/apple/swift-evolution/blob/master/proposals/0073-noescape-once.md

Actually that proposal was for noescape closures and this suggestion is
for escaping closures. I don't think the compiler can verify this for
noescape closures. If it is possible it would be far more complicated.

On Sun, Jun 5, 2016 at 11:37 AM, Charles Srstka via swift-evolution < > swift-evolution@swift.org> wrote:

MOTIVATION:

As per the current situation, there is a pitfall when writing
asynchronous APIs that does not occur when writing synchronous APIs.
Consider the following synchronous API:

func doSomething() -> SomeEnum {
        if aCondition {
                if bCondition {
                        return .Foo
                } else {
                        return .Bar
                }
        } else {
                if cCondition {
                        return .Baz
                }
        }
}

The compiler will give an error here, since if both aCondition and
cCondition are false, the function will not return anything.

However, consider the equivalent async API:

func doSomething(completionHandler: (SomeEnum) -> ()) {
        dispatch_async(someQueue) {
                if aCondition {
                        if bCondition {
                                completionHandler(.Foo)
                        } else {
                                completionHandler(.Bar)
                        }
                } else {
                        if cCondition {
                                completionHandler(.Baz)
                        }
                }
        }
}

Whoops, now the function can return without ever firing its completion
handler, and the problem might not be discovered until runtime (and,
depending on the complexity of the function, may be hard to find).

PROPOSED SOLUTION:

Add a @required attribute that can be applied to closure arguments. This
attribute simply states that the given closure will always be eventually
called, and the compiler can enforce this.

DETAILED DESIGN:

- The @required attribute states in our API contract that a given closure
*must* be called at some point after the function is called.

- Standard API calls like dispatch_async that contractually promise to
execute a closure or block get @required added to their signatures.

- When the compiler sees a @required closure in a function declaration,
it checks to make sure that every execution path either calls the closure
at some point, or sends a @required closure to another API that eventually
ends up calling the closure.

- If there’s a way for a @required closure not to be called, the compiler
emits an error letting the developer know about the bug in his/her code.

IMPACT ON EXISTING CODE:

None. This is purely additive.

ALTERNATIVES CONSIDERED:

I got nothin’.

Charles

_______________________________________________
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


(Matthew Johnson) #8

I like this.

One of the suggestions on @noescape(once) was that it just becomes @once and works with escaping closures too. It might be possible if compile time checks verified that the closure isn't copied, and that it is called before being deinit-ialized. Failing that I'm happy with a runtime circumstance in the cases the compiler can't check.

Yeah, maybe if it is only used asynchronously and never stored in a member or global it could be verified and that is a pretty common case. That would certainly be easier than the general case.

I prefer @once over @required if the guarantee is single execution. If the guarantee is *at least once* obviously @once is not the right attribute, but I'm not convinced @required is either. Maybe @invoked.

···

Sent from my iPad

On Jun 5, 2016, at 6:56 AM, Andrew Bennett <cacoyi@gmail.com> wrote:

It would be great if @required took into the account the feedback from that proposal and considered the synchronous case too.

As an aside, you can get some of the guarantees you want like this:

func doSomething(completionHandler: (SomeEnum) -> ()) {
  dispatch_async(someQueue) {
    let result: SomeEnum
    // the compiler ensures 'result' is set
    defer { completionHandler(result) }

    if aCondition {
      if bCondition {
        result = .Foo
      } else {
        result = .Bar
      }
      // the compiler ensures you do this, because it is 'let'
      return
    }

    if cCondition {
      result = .Baz
    }
  }
}

On Sun, Jun 5, 2016 at 9:42 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

Sent from my iPad

On Jun 5, 2016, at 5:02 AM, Patrick Pijnappel via swift-evolution <swift-evolution@swift.org> wrote:

This has actually been proposed before, see SE-0073: https://github.com/apple/swift-evolution/blob/master/proposals/0073-noescape-once.md

Actually that proposal was for noescape closures and this suggestion is for escaping closures. I don't think the compiler can verify this for noescape closures. If it is possible it would be far more complicated.

On Sun, Jun 5, 2016 at 11:37 AM, Charles Srstka via swift-evolution <swift-evolution@swift.org> wrote:
MOTIVATION:

As per the current situation, there is a pitfall when writing asynchronous APIs that does not occur when writing synchronous APIs. Consider the following synchronous API:

func doSomething() -> SomeEnum {
        if aCondition {
                if bCondition {
                        return .Foo
                } else {
                        return .Bar
                }
        } else {
                if cCondition {
                        return .Baz
                }
        }
}

The compiler will give an error here, since if both aCondition and cCondition are false, the function will not return anything.

However, consider the equivalent async API:

func doSomething(completionHandler: (SomeEnum) -> ()) {
        dispatch_async(someQueue) {
                if aCondition {
                        if bCondition {
                                completionHandler(.Foo)
                        } else {
                                completionHandler(.Bar)
                        }
                } else {
                        if cCondition {
                                completionHandler(.Baz)
                        }
                }
        }
}

Whoops, now the function can return without ever firing its completion handler, and the problem might not be discovered until runtime (and, depending on the complexity of the function, may be hard to find).

PROPOSED SOLUTION:

Add a @required attribute that can be applied to closure arguments. This attribute simply states that the given closure will always be eventually called, and the compiler can enforce this.

DETAILED DESIGN:

- The @required attribute states in our API contract that a given closure *must* be called at some point after the function is called.

- Standard API calls like dispatch_async that contractually promise to execute a closure or block get @required added to their signatures.

- When the compiler sees a @required closure in a function declaration, it checks to make sure that every execution path either calls the closure at some point, or sends a @required closure to another API that eventually ends up calling the closure.

- If there’s a way for a @required closure not to be called, the compiler emits an error letting the developer know about the bug in his/her code.

IMPACT ON EXISTING CODE:

None. This is purely additive.

ALTERNATIVES CONSIDERED:

I got nothin’.

Charles

_______________________________________________
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


(Andrew Bennett) #9

"runtime circumstance" -> "runtime assertion", weird typo.

···

On Sun, Jun 5, 2016 at 9:56 PM, Andrew Bennett <cacoyi@gmail.com> wrote:

I like this.

One of the suggestions on @noescape(once) was that it just becomes @once
and works with escaping closures too. It might be possible if compile time
checks verified that the closure isn't copied, and that it is called before
being deinit-ialized. Failing that I'm happy with a runtime circumstance in
the cases the compiler can't check.

It would be great if @required took into the account the feedback from
that proposal and considered the synchronous case too.

As an aside, you can get some of the guarantees you want like this:

func doSomething(completionHandler: (SomeEnum) -> ()) {

  dispatch_async(someQueue) {

    let result: SomeEnum

    // the compiler ensures 'result' is set

    defer { completionHandler(result) }

    if aCondition {

      if bCondition {

        result = .Foo

      } else {

        result = .Bar

      }

      // the compiler ensures you do this, because it is 'let'

      return
    }

    if cCondition {

      result = .Baz

    }

  }

}

On Sun, Jun 5, 2016 at 9:42 PM, Matthew Johnson via swift-evolution < > swift-evolution@swift.org> wrote:

Sent from my iPad

On Jun 5, 2016, at 5:02 AM, Patrick Pijnappel via swift-evolution < >> swift-evolution@swift.org> wrote:

This has actually been proposed before, see SE-0073:
https://github.com/apple/swift-evolution/blob/master/proposals/0073-noescape-once.md

Actually that proposal was for noescape closures and this suggestion is
for escaping closures. I don't think the compiler can verify this for
noescape closures. If it is possible it would be far more complicated.

On Sun, Jun 5, 2016 at 11:37 AM, Charles Srstka via swift-evolution < >> swift-evolution@swift.org> wrote:

MOTIVATION:

As per the current situation, there is a pitfall when writing
asynchronous APIs that does not occur when writing synchronous APIs.
Consider the following synchronous API:

func doSomething() -> SomeEnum {
        if aCondition {
                if bCondition {
                        return .Foo
                } else {
                        return .Bar
                }
        } else {
                if cCondition {
                        return .Baz
                }
        }
}

The compiler will give an error here, since if both aCondition and
cCondition are false, the function will not return anything.

However, consider the equivalent async API:

func doSomething(completionHandler: (SomeEnum) -> ()) {
        dispatch_async(someQueue) {
                if aCondition {
                        if bCondition {
                                completionHandler(.Foo)
                        } else {
                                completionHandler(.Bar)
                        }
                } else {
                        if cCondition {
                                completionHandler(.Baz)
                        }
                }
        }
}

Whoops, now the function can return without ever firing its completion
handler, and the problem might not be discovered until runtime (and,
depending on the complexity of the function, may be hard to find).

PROPOSED SOLUTION:

Add a @required attribute that can be applied to closure arguments. This
attribute simply states that the given closure will always be eventually
called, and the compiler can enforce this.

DETAILED DESIGN:

- The @required attribute states in our API contract that a given
closure *must* be called at some point after the function is called.

- Standard API calls like dispatch_async that contractually promise to
execute a closure or block get @required added to their signatures.

- When the compiler sees a @required closure in a function declaration,
it checks to make sure that every execution path either calls the closure
at some point, or sends a @required closure to another API that eventually
ends up calling the closure.

- If there’s a way for a @required closure not to be called, the
compiler emits an error letting the developer know about the bug in his/her
code.

IMPACT ON EXISTING CODE:

None. This is purely additive.

ALTERNATIVES CONSIDERED:

I got nothin’.

Charles

_______________________________________________
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


(Andrew Bennett) #10

Storing into a member would be fine, as long as it must keep @once as a
type annotation and the compiler makes sure you maintain:
    sum(callCount, storeCount, passCount) == 1

For example:
  class Example {
    private var closure: (@once (T) -> Void)?

    func callClosure(value: T, replace: (@once (T) -> Void)? = nil) {

      // the compiler should error if it detects the closure:

      // * escaping more than once, while still being stored,

      // * or being called while still being stored or escaping,

      // * or being overwritten without being called

      if let closure = self.closure {

        self.closure = replace

        closure(value)

      }

    }

    deinit {

      // compiler warning: that closure is potentially un-called

      // runtime fatalError if it's .Some(Closure) after deinit

    }

  }

There could be a standard library type with those guarantees built in.

···

On Sun, Jun 5, 2016 at 10:12 PM, Matthew Johnson <matthew@anandabits.com> wrote:

Sent from my iPad

On Jun 5, 2016, at 6:56 AM, Andrew Bennett <cacoyi@gmail.com> wrote:

I like this.

One of the suggestions on @noescape(once) was that it just becomes @once
and works with escaping closures too. It might be possible if compile time
checks verified that the closure isn't copied, and that it is called before
being deinit-ialized. Failing that I'm happy with a runtime circumstance in
the cases the compiler can't check.

Yeah, maybe if it is only used asynchronously and never stored in a member
or global it could be verified and that is a pretty common case. That
would certainly be easier than the general case.

I prefer @once over @required if the guarantee is single execution. If
the guarantee is *at least once* obviously @once is not the right
attribute, but I'm not convinced @required is either. Maybe @invoked.

It would be great if @required took into the account the feedback from
that proposal and considered the synchronous case too.

As an aside, you can get some of the guarantees you want like this:

func doSomething(completionHandler: (SomeEnum) -> ()) {

  dispatch_async(someQueue) {

    let result: SomeEnum

    // the compiler ensures 'result' is set

    defer { completionHandler(result) }

    if aCondition {

      if bCondition {

        result = .Foo

      } else {

        result = .Bar

      }

      // the compiler ensures you do this, because it is 'let'

      return
    }

    if cCondition {

      result = .Baz

    }

  }

}

On Sun, Jun 5, 2016 at 9:42 PM, Matthew Johnson via swift-evolution < > swift-evolution@swift.org> wrote:

Sent from my iPad

On Jun 5, 2016, at 5:02 AM, Patrick Pijnappel via swift-evolution < >> swift-evolution@swift.org> wrote:

This has actually been proposed before, see SE-0073:
https://github.com/apple/swift-evolution/blob/master/proposals/0073-noescape-once.md

Actually that proposal was for noescape closures and this suggestion is
for escaping closures. I don't think the compiler can verify this for
noescape closures. If it is possible it would be far more complicated.

On Sun, Jun 5, 2016 at 11:37 AM, Charles Srstka via swift-evolution < >> swift-evolution@swift.org> wrote:

MOTIVATION:

As per the current situation, there is a pitfall when writing
asynchronous APIs that does not occur when writing synchronous APIs.
Consider the following synchronous API:

func doSomething() -> SomeEnum {
        if aCondition {
                if bCondition {
                        return .Foo
                } else {
                        return .Bar
                }
        } else {
                if cCondition {
                        return .Baz
                }
        }
}

The compiler will give an error here, since if both aCondition and
cCondition are false, the function will not return anything.

However, consider the equivalent async API:

func doSomething(completionHandler: (SomeEnum) -> ()) {
        dispatch_async(someQueue) {
                if aCondition {
                        if bCondition {
                                completionHandler(.Foo)
                        } else {
                                completionHandler(.Bar)
                        }
                } else {
                        if cCondition {
                                completionHandler(.Baz)
                        }
                }
        }
}

Whoops, now the function can return without ever firing its completion
handler, and the problem might not be discovered until runtime (and,
depending on the complexity of the function, may be hard to find).

PROPOSED SOLUTION:

Add a @required attribute that can be applied to closure arguments. This
attribute simply states that the given closure will always be eventually
called, and the compiler can enforce this.

DETAILED DESIGN:

- The @required attribute states in our API contract that a given
closure *must* be called at some point after the function is called.

- Standard API calls like dispatch_async that contractually promise to
execute a closure or block get @required added to their signatures.

- When the compiler sees a @required closure in a function declaration,
it checks to make sure that every execution path either calls the closure
at some point, or sends a @required closure to another API that eventually
ends up calling the closure.

- If there’s a way for a @required closure not to be called, the
compiler emits an error letting the developer know about the bug in his/her
code.

IMPACT ON EXISTING CODE:

None. This is purely additive.

ALTERNATIVES CONSIDERED:

I got nothin’.

Charles

_______________________________________________
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


(Michael Peternell) #11

I agree that it may help in a few cases. But I think the change is "not significant enough to warrant a change in Swift". It adds yet another keyword to the language that every new dev has to learn about, and the problem it solves can more elegantly be solved by writing more elegant code.

-Michael

···

Am 05.06.2016 um 20:31 schrieb Charlie Monroe via swift-evolution <swift-evolution@swift.org>:

While I agree with Michael that nowadays, a lot of stuff that doesn't need to be, is done async, which leads to a giant thread pool per app and developers nowadays do not think of the cost of inter-thread communication (i.e. each dispatch_(a)sync has its cost, even though it's a light-weight thread), I agree with Charles that something like suggested does indeed help debugging issues with multi-thread apps.


(Matthew Johnson) #12

Storing into a member would be fine, as long as it must keep @once as a type annotation and the compiler makes sure you maintain:
    sum(callCount, storeCount, passCount) == 1

For example:
  class Example {
    private var closure: (@once (T) -> Void)?

    func callClosure(value: T, replace: (@once (T) -> Void)? = nil) {
      // the compiler should error if it detects the closure:
      // * escaping more than once, while still being stored,
      // * or being called while still being stored or escaping,
      // * or being overwritten without being called
      if let closure = self.closure {
        self.closure = replace
        closure(value)
      }
    }

    deinit {
      // compiler warning: that closure is potentially un-called
      // runtime fatalError if it's .Some(Closure) after deinit
    }
  }

There could be a standard library type with those guarantees built in.

I don't consider this compiler verification. It is runtime verification. The best the compiler can do is enforce constraints that allow for guaranteed runtime verification. You can argue that is better than nothing but it is not a static guarantee of correct behavior.

···

Sent from my iPad

On Jun 5, 2016, at 8:52 AM, Andrew Bennett <cacoyi@gmail.com> wrote:

On Sun, Jun 5, 2016 at 10:12 PM, Matthew Johnson <matthew@anandabits.com> wrote:

Sent from my iPad

On Jun 5, 2016, at 6:56 AM, Andrew Bennett <cacoyi@gmail.com> wrote:

I like this.

One of the suggestions on @noescape(once) was that it just becomes @once and works with escaping closures too. It might be possible if compile time checks verified that the closure isn't copied, and that it is called before being deinit-ialized. Failing that I'm happy with a runtime circumstance in the cases the compiler can't check.

Yeah, maybe if it is only used asynchronously and never stored in a member or global it could be verified and that is a pretty common case. That would certainly be easier than the general case.

I prefer @once over @required if the guarantee is single execution. If the guarantee is *at least once* obviously @once is not the right attribute, but I'm not convinced @required is either. Maybe @invoked.

It would be great if @required took into the account the feedback from that proposal and considered the synchronous case too.

As an aside, you can get some of the guarantees you want like this:

func doSomething(completionHandler: (SomeEnum) -> ()) {
  dispatch_async(someQueue) {
    let result: SomeEnum
    // the compiler ensures 'result' is set
    defer { completionHandler(result) }

    if aCondition {
      if bCondition {
        result = .Foo
      } else {
        result = .Bar
      }
      // the compiler ensures you do this, because it is 'let'
      return
    }

    if cCondition {
      result = .Baz
    }
  }
}

On Sun, Jun 5, 2016 at 9:42 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

Sent from my iPad

On Jun 5, 2016, at 5:02 AM, Patrick Pijnappel via swift-evolution <swift-evolution@swift.org> wrote:

This has actually been proposed before, see SE-0073: https://github.com/apple/swift-evolution/blob/master/proposals/0073-noescape-once.md

Actually that proposal was for noescape closures and this suggestion is for escaping closures. I don't think the compiler can verify this for noescape closures. If it is possible it would be far more complicated.

On Sun, Jun 5, 2016 at 11:37 AM, Charles Srstka via swift-evolution <swift-evolution@swift.org> wrote:
MOTIVATION:

As per the current situation, there is a pitfall when writing asynchronous APIs that does not occur when writing synchronous APIs. Consider the following synchronous API:

func doSomething() -> SomeEnum {
        if aCondition {
                if bCondition {
                        return .Foo
                } else {
                        return .Bar
                }
        } else {
                if cCondition {
                        return .Baz
                }
        }
}

The compiler will give an error here, since if both aCondition and cCondition are false, the function will not return anything.

However, consider the equivalent async API:

func doSomething(completionHandler: (SomeEnum) -> ()) {
        dispatch_async(someQueue) {
                if aCondition {
                        if bCondition {
                                completionHandler(.Foo)
                        } else {
                                completionHandler(.Bar)
                        }
                } else {
                        if cCondition {
                                completionHandler(.Baz)
                        }
                }
        }
}

Whoops, now the function can return without ever firing its completion handler, and the problem might not be discovered until runtime (and, depending on the complexity of the function, may be hard to find).

PROPOSED SOLUTION:

Add a @required attribute that can be applied to closure arguments. This attribute simply states that the given closure will always be eventually called, and the compiler can enforce this.

DETAILED DESIGN:

- The @required attribute states in our API contract that a given closure *must* be called at some point after the function is called.

- Standard API calls like dispatch_async that contractually promise to execute a closure or block get @required added to their signatures.

- When the compiler sees a @required closure in a function declaration, it checks to make sure that every execution path either calls the closure at some point, or sends a @required closure to another API that eventually ends up calling the closure.

- If there’s a way for a @required closure not to be called, the compiler emits an error letting the developer know about the bug in his/her code.

IMPACT ON EXISTING CODE:

None. This is purely additive.

ALTERNATIVES CONSIDERED:

I got nothin’.

Charles

_______________________________________________
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


(Charles Srstka) #13

Okay, what’s the “more elegant” way to write a function that uses networking or XPC, or that requires user feedback from a sheet?

Charles

···

On Jun 5, 2016, at 5:46 PM, michael.peternell@gmx.at wrote:

Am 05.06.2016 um 20:31 schrieb Charlie Monroe via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

While I agree with Michael that nowadays, a lot of stuff that doesn't need to be, is done async, which leads to a giant thread pool per app and developers nowadays do not think of the cost of inter-thread communication (i.e. each dispatch_(a)sync has its cost, even though it's a light-weight thread), I agree with Charles that something like suggested does indeed help debugging issues with multi-thread apps.

I agree that it may help in a few cases. But I think the change is "not significant enough to warrant a change in Swift". It adds yet another keyword to the language that every new dev has to learn about, and the problem it solves can more elegantly be solved by writing more elegant code.


(Andrew Bennett) #14

Perhaps I was unclear, in my explanation. The guarantee I'm enforcing is
that the closure is called exactly once before being released.

Everything I suggested is a compile-time check.

The compile-time warning and runtime `fatalError` I suggested could be
replaced with a compile-time error, however even in this case it is still
statically checked for the warning.

The compiler can statically guarantee *exactly one* of these things happens
in methods using the closure:

   - the closure is *called*
   - the closure is *stored*
   - the closure is *passed* to another method
   - the program *aborts* with something like a fatalError

If the closure is stored then there must be a *deinit*, and those checks
apply there as well.

I believe this is sufficient to ensure the closure is called once. Please
let me know if there are any cases these checks miss.

···

On Sun, Jun 5, 2016 at 11:59 PM, Matthew Johnson <matthew@anandabits.com> wrote:

Sent from my iPad

On Jun 5, 2016, at 8:52 AM, Andrew Bennett <cacoyi@gmail.com> wrote:

Storing into a member would be fine, as long as it must keep @once as a
type annotation and the compiler makes sure you maintain:
    sum(callCount, storeCount, passCount) == 1

For example:
  class Example {
    private var closure: (@once (T) -> Void)?

    func callClosure(value: T, replace: (@once (T) -> Void)? = nil) {

      // the compiler should error if it detects the closure:

      // * escaping more than once, while still being stored,

      // * or being called while still being stored or escaping,

      // * or being overwritten without being called

      if let closure = self.closure {

        self.closure = replace

        closure(value)

      }

    }

    deinit {

      // compiler warning: that closure is potentially un-called

      // runtime fatalError if it's .Some(Closure) after deinit

    }

  }

There could be a standard library type with those guarantees built in.

I don't consider this compiler verification. It is runtime verification.
The best the compiler can do is enforce constraints that allow for
guaranteed runtime verification. You can argue that is better than nothing
but it is not a static guarantee of correct behavior.

On Sun, Jun 5, 2016 at 10:12 PM, Matthew Johnson <matthew@anandabits.com> > wrote:

Sent from my iPad

On Jun 5, 2016, at 6:56 AM, Andrew Bennett <cacoyi@gmail.com> wrote:

I like this.

One of the suggestions on @noescape(once) was that it just becomes @once
and works with escaping closures too. It might be possible if compile time
checks verified that the closure isn't copied, and that it is called before
being deinit-ialized. Failing that I'm happy with a runtime circumstance in
the cases the compiler can't check.

Yeah, maybe if it is only used asynchronously and never stored in a
member or global it could be verified and that is a pretty common case.
That would certainly be easier than the general case.

I prefer @once over @required if the guarantee is single execution. If
the guarantee is *at least once* obviously @once is not the right
attribute, but I'm not convinced @required is either. Maybe @invoked.

It would be great if @required took into the account the feedback from
that proposal and considered the synchronous case too.

As an aside, you can get some of the guarantees you want like this:

func doSomething(completionHandler: (SomeEnum) -> ()) {

  dispatch_async(someQueue) {

    let result: SomeEnum

    // the compiler ensures 'result' is set

    defer { completionHandler(result) }

    if aCondition {

      if bCondition {

        result = .Foo

      } else {

        result = .Bar

      }

      // the compiler ensures you do this, because it is 'let'

      return
    }

    if cCondition {

      result = .Baz

    }

  }

}

On Sun, Jun 5, 2016 at 9:42 PM, Matthew Johnson via swift-evolution < >> swift-evolution@swift.org> wrote:

Sent from my iPad

On Jun 5, 2016, at 5:02 AM, Patrick Pijnappel via swift-evolution < >>> swift-evolution@swift.org> wrote:

This has actually been proposed before, see SE-0073:
https://github.com/apple/swift-evolution/blob/master/proposals/0073-noescape-once.md

Actually that proposal was for noescape closures and this suggestion is
for escaping closures. I don't think the compiler can verify this for
noescape closures. If it is possible it would be far more complicated.

On Sun, Jun 5, 2016 at 11:37 AM, Charles Srstka via swift-evolution < >>> swift-evolution@swift.org> wrote:

MOTIVATION:

As per the current situation, there is a pitfall when writing
asynchronous APIs that does not occur when writing synchronous APIs.
Consider the following synchronous API:

func doSomething() -> SomeEnum {
        if aCondition {
                if bCondition {
                        return .Foo
                } else {
                        return .Bar
                }
        } else {
                if cCondition {
                        return .Baz
                }
        }
}

The compiler will give an error here, since if both aCondition and
cCondition are false, the function will not return anything.

However, consider the equivalent async API:

func doSomething(completionHandler: (SomeEnum) -> ()) {
        dispatch_async(someQueue) {
                if aCondition {
                        if bCondition {
                                completionHandler(.Foo)
                        } else {
                                completionHandler(.Bar)
                        }
                } else {
                        if cCondition {
                                completionHandler(.Baz)
                        }
                }
        }
}

Whoops, now the function can return without ever firing its completion
handler, and the problem might not be discovered until runtime (and,
depending on the complexity of the function, may be hard to find).

PROPOSED SOLUTION:

Add a @required attribute that can be applied to closure arguments.
This attribute simply states that the given closure will always be
eventually called, and the compiler can enforce this.

DETAILED DESIGN:

- The @required attribute states in our API contract that a given
closure *must* be called at some point after the function is called.

- Standard API calls like dispatch_async that contractually promise to
execute a closure or block get @required added to their signatures.

- When the compiler sees a @required closure in a function declaration,
it checks to make sure that every execution path either calls the closure
at some point, or sends a @required closure to another API that eventually
ends up calling the closure.

- If there’s a way for a @required closure not to be called, the
compiler emits an error letting the developer know about the bug in his/her
code.

IMPACT ON EXISTING CODE:

None. This is purely additive.

ALTERNATIVES CONSIDERED:

I got nothin’.

Charles

_______________________________________________
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


(Michael Peternell) #15

While I agree with Michael that nowadays, a lot of stuff that doesn't need to be, is done async, which leads to a giant thread pool per app and developers nowadays do not think of the cost of inter-thread communication (i.e. each dispatch_(a)sync has its cost, even though it's a light-weight thread), I agree with Charles that something like suggested does indeed help debugging issues with multi-thread apps.

I agree that it may help in a few cases. But I think the change is "not significant enough to warrant a change in Swift". It adds yet another keyword to the language that every new dev has to learn about, and the problem it solves can more elegantly be solved by writing more elegant code.

Okay, what’s the “more elegant” way to write a function that uses networking or XPC, or that requires user feedback from a sheet?

That's really hard to answer in the general case. I think real proposals should contain concrete, realistic examples that show the benefit of the proposal. It's really hard to argue against a proposal if there is no such example. User feedback from a sheet is one of the few examples where asynchronous programming makes sense: But I cannot see how a `@required` annotation would be useful in that setting.

-Michael

···

Am 06.06.2016 um 00:59 schrieb Charles Srstka <cocoadev@charlessoft.com>:

On Jun 5, 2016, at 5:46 PM, michael.peternell@gmx.at wrote:

Am 05.06.2016 um 20:31 schrieb Charlie Monroe via swift-evolution <swift-evolution@swift.org>:

Charles


(Matthew Johnson) #16

Perhaps I was unclear, in my explanation. The guarantee I'm enforcing is that the closure is called exactly once before being released.

Everything I suggested is a compile-time check.

The compile-time warning and runtime `fatalError` I suggested could be replaced with a compile-time error, however even in this case it is still statically checked for the warning.

The compiler can statically guarantee exactly one of these things happens in methods using the closure:
the closure is called
the closure is stored
the closure is passed to another method
the program aborts with something like a fatalError
If the closure is stored then there must be a deinit, and those checks apply there as well.

I believe this is sufficient to ensure the closure is called once. Please let me know if there are any cases these checks miss.

If the closure is stored in a member it could be called in the implementation of any other member. Calls to other members could come from arbitrary locations in the surrounding program at arbitrary points in time (unless you have static analysis that can prove a narrower set of possibilities). And if you have a model that relies on behavior in a deinit then storing the closure won't be possible for structs. You have also missed the case that the closure is captured by another closure (maybe it is a completion block and you call it in a completion block of a method your method calls).

···

Sent from my iPad

On Jun 5, 2016, at 6:50 PM, Andrew Bennett <cacoyi@gmail.com> wrote:

On Sun, Jun 5, 2016 at 11:59 PM, Matthew Johnson <matthew@anandabits.com> wrote:

Sent from my iPad

On Jun 5, 2016, at 8:52 AM, Andrew Bennett <cacoyi@gmail.com> wrote:

Storing into a member would be fine, as long as it must keep @once as a type annotation and the compiler makes sure you maintain:
    sum(callCount, storeCount, passCount) == 1

For example:
  class Example {
    private var closure: (@once (T) -> Void)?

    func callClosure(value: T, replace: (@once (T) -> Void)? = nil) {
      // the compiler should error if it detects the closure:
      // * escaping more than once, while still being stored,
      // * or being called while still being stored or escaping,
      // * or being overwritten without being called
      if let closure = self.closure {
        self.closure = replace
        closure(value)
      }
    }

    deinit {
      // compiler warning: that closure is potentially un-called
      // runtime fatalError if it's .Some(Closure) after deinit
    }
  }

There could be a standard library type with those guarantees built in.

I don't consider this compiler verification. It is runtime verification. The best the compiler can do is enforce constraints that allow for guaranteed runtime verification. You can argue that is better than nothing but it is not a static guarantee of correct behavior.

On Sun, Jun 5, 2016 at 10:12 PM, Matthew Johnson <matthew@anandabits.com> wrote:

Sent from my iPad

On Jun 5, 2016, at 6:56 AM, Andrew Bennett <cacoyi@gmail.com> wrote:

I like this.

One of the suggestions on @noescape(once) was that it just becomes @once and works with escaping closures too. It might be possible if compile time checks verified that the closure isn't copied, and that it is called before being deinit-ialized. Failing that I'm happy with a runtime circumstance in the cases the compiler can't check.

Yeah, maybe if it is only used asynchronously and never stored in a member or global it could be verified and that is a pretty common case. That would certainly be easier than the general case.

I prefer @once over @required if the guarantee is single execution. If the guarantee is *at least once* obviously @once is not the right attribute, but I'm not convinced @required is either. Maybe @invoked.

It would be great if @required took into the account the feedback from that proposal and considered the synchronous case too.

As an aside, you can get some of the guarantees you want like this:

func doSomething(completionHandler: (SomeEnum) -> ()) {
  dispatch_async(someQueue) {
    let result: SomeEnum
    // the compiler ensures 'result' is set
    defer { completionHandler(result) }

    if aCondition {
      if bCondition {
        result = .Foo
      } else {
        result = .Bar
      }
      // the compiler ensures you do this, because it is 'let'
      return
    }

    if cCondition {
      result = .Baz
    }
  }
}

On Sun, Jun 5, 2016 at 9:42 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

Sent from my iPad

On Jun 5, 2016, at 5:02 AM, Patrick Pijnappel via swift-evolution <swift-evolution@swift.org> wrote:

This has actually been proposed before, see SE-0073: https://github.com/apple/swift-evolution/blob/master/proposals/0073-noescape-once.md

Actually that proposal was for noescape closures and this suggestion is for escaping closures. I don't think the compiler can verify this for noescape closures. If it is possible it would be far more complicated.

On Sun, Jun 5, 2016 at 11:37 AM, Charles Srstka via swift-evolution <swift-evolution@swift.org> wrote:
MOTIVATION:

As per the current situation, there is a pitfall when writing asynchronous APIs that does not occur when writing synchronous APIs. Consider the following synchronous API:

func doSomething() -> SomeEnum {
        if aCondition {
                if bCondition {
                        return .Foo
                } else {
                        return .Bar
                }
        } else {
                if cCondition {
                        return .Baz
                }
        }
}

The compiler will give an error here, since if both aCondition and cCondition are false, the function will not return anything.

However, consider the equivalent async API:

func doSomething(completionHandler: (SomeEnum) -> ()) {
        dispatch_async(someQueue) {
                if aCondition {
                        if bCondition {
                                completionHandler(.Foo)
                        } else {
                                completionHandler(.Bar)
                        }
                } else {
                        if cCondition {
                                completionHandler(.Baz)
                        }
                }
        }
}

Whoops, now the function can return without ever firing its completion handler, and the problem might not be discovered until runtime (and, depending on the complexity of the function, may be hard to find).

PROPOSED SOLUTION:

Add a @required attribute that can be applied to closure arguments. This attribute simply states that the given closure will always be eventually called, and the compiler can enforce this.

DETAILED DESIGN:

- The @required attribute states in our API contract that a given closure *must* be called at some point after the function is called.

- Standard API calls like dispatch_async that contractually promise to execute a closure or block get @required added to their signatures.

- When the compiler sees a @required closure in a function declaration, it checks to make sure that every execution path either calls the closure at some point, or sends a @required closure to another API that eventually ends up calling the closure.

- If there’s a way for a @required closure not to be called, the compiler emits an error letting the developer know about the bug in his/her code.

IMPACT ON EXISTING CODE:

None. This is purely additive.

ALTERNATIVES CONSIDERED:

I got nothin’.

Charles

_______________________________________________
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


(Andrew Bennett) #17

Thanks Matthew, my responses are inline:

Sent from my iPad

Perhaps I was unclear, in my explanation. The guarantee I'm enforcing is
that the closure is called exactly once before being released.

Everything I suggested is a compile-time check.

The compile-time warning and runtime `fatalError` I suggested could be
replaced with a compile-time error, however even in this case it is still
statically checked for the warning.

The compiler can statically guarantee *exactly one* of these things
happens in methods using the closure:

   - the closure is *called*
   - the closure is *stored*
   - the closure is *passed* to another method
   - the program *aborts* with something like a fatalError

If the closure is stored then there must be a *deinit*, and those checks
apply there as well.

I believe this is sufficient to ensure the closure is called once. Please
let me know if there are any cases these checks miss.

If the closure is stored in a member it could be called in the
implementation of any other member. Calls to other members could come from
arbitrary locations in the surrounding program at arbitrary points in time
(unless you have static analysis that can prove a narrower set of
possibilities).

This isn't a problem if the member has to have the type annotation, all
uses of the member, whether from elsewhere in the program, or other
methods, would have to pass the checks.

If you call the closure you must nil/replace the member.

And if you have a model that relies on behavior in a deinit then storing

the closure won't be possible for structs.

This is true, considering you don't want to copy a @once closure you
probably don't want value-type semantics anyway.

You have also missed the case that the closure is captured by another

closure (maybe it is a completion block and you call it in a completion
block of a method your method calls).

This is correct. I forgot to mention that I'm sorry, thanks for pointing it
out!

I was thinking that a @once closure can only be captured by another @once
closure. We can add that as another dot-point:

   - the closure is captured by another @once closure, this is the only
   time it can be captured.

···

On Mon, Jun 6, 2016 at 10:32 AM, Matthew Johnson <matthew@anandabits.com> wrote:

On Jun 5, 2016, at 6:50 PM, Andrew Bennett <cacoyi@gmail.com> wrote:

On Sun, Jun 5, 2016 at 11:59 PM, Matthew Johnson <matthew@anandabits.com> > wrote:

Sent from my iPad

On Jun 5, 2016, at 8:52 AM, Andrew Bennett <cacoyi@gmail.com> wrote:

Storing into a member would be fine, as long as it must keep @once as a
type annotation and the compiler makes sure you maintain:
    sum(callCount, storeCount, passCount) == 1

For example:
  class Example {
    private var closure: (@once (T) -> Void)?

    func callClosure(value: T, replace: (@once (T) -> Void)? = nil) {

      // the compiler should error if it detects the closure:

      // * escaping more than once, while still being stored,

      // * or being called while still being stored or escaping,

      // * or being overwritten without being called

      if let closure = self.closure {

        self.closure = replace

        closure(value)

      }

    }

    deinit {

      // compiler warning: that closure is potentially un-called

      // runtime fatalError if it's .Some(Closure) after deinit

    }

  }

There could be a standard library type with those guarantees built in.

I don't consider this compiler verification. It is runtime
verification. The best the compiler can do is enforce constraints that
allow for guaranteed runtime verification. You can argue that is better
than nothing but it is not a static guarantee of correct behavior.

On Sun, Jun 5, 2016 at 10:12 PM, Matthew Johnson <matthew@anandabits.com> >> wrote:

Sent from my iPad

On Jun 5, 2016, at 6:56 AM, Andrew Bennett <cacoyi@gmail.com> wrote:

I like this.

One of the suggestions on @noescape(once) was that it just becomes @once
and works with escaping closures too. It might be possible if compile time
checks verified that the closure isn't copied, and that it is called before
being deinit-ialized. Failing that I'm happy with a runtime circumstance in
the cases the compiler can't check.

Yeah, maybe if it is only used asynchronously and never stored in a
member or global it could be verified and that is a pretty common case.
That would certainly be easier than the general case.

I prefer @once over @required if the guarantee is single execution. If
the guarantee is *at least once* obviously @once is not the right
attribute, but I'm not convinced @required is either. Maybe @invoked.

It would be great if @required took into the account the feedback from
that proposal and considered the synchronous case too.

As an aside, you can get some of the guarantees you want like this:

func doSomething(completionHandler: (SomeEnum) -> ()) {

  dispatch_async(someQueue) {

    let result: SomeEnum

    // the compiler ensures 'result' is set

    defer { completionHandler(result) }

    if aCondition {

      if bCondition {

        result = .Foo

      } else {

        result = .Bar

      }

      // the compiler ensures you do this, because it is 'let'

      return
    }

    if cCondition {

      result = .Baz

    }

  }

}

On Sun, Jun 5, 2016 at 9:42 PM, Matthew Johnson via swift-evolution < >>> swift-evolution@swift.org> wrote:

Sent from my iPad

On Jun 5, 2016, at 5:02 AM, Patrick Pijnappel via swift-evolution < >>>> swift-evolution@swift.org> wrote:

This has actually been proposed before, see SE-0073:
https://github.com/apple/swift-evolution/blob/master/proposals/0073-noescape-once.md

Actually that proposal was for noescape closures and this suggestion is
for escaping closures. I don't think the compiler can verify this for
noescape closures. If it is possible it would be far more complicated.

On Sun, Jun 5, 2016 at 11:37 AM, Charles Srstka via swift-evolution < >>>> swift-evolution@swift.org> wrote:

MOTIVATION:

As per the current situation, there is a pitfall when writing
asynchronous APIs that does not occur when writing synchronous APIs.
Consider the following synchronous API:

func doSomething() -> SomeEnum {
        if aCondition {
                if bCondition {
                        return .Foo
                } else {
                        return .Bar
                }
        } else {
                if cCondition {
                        return .Baz
                }
        }
}

The compiler will give an error here, since if both aCondition and
cCondition are false, the function will not return anything.

However, consider the equivalent async API:

func doSomething(completionHandler: (SomeEnum) -> ()) {
        dispatch_async(someQueue) {
                if aCondition {
                        if bCondition {
                                completionHandler(.Foo)
                        } else {
                                completionHandler(.Bar)
                        }
                } else {
                        if cCondition {
                                completionHandler(.Baz)
                        }
                }
        }
}

Whoops, now the function can return without ever firing its completion
handler, and the problem might not be discovered until runtime (and,
depending on the complexity of the function, may be hard to find).

PROPOSED SOLUTION:

Add a @required attribute that can be applied to closure arguments.
This attribute simply states that the given closure will always be
eventually called, and the compiler can enforce this.

DETAILED DESIGN:

- The @required attribute states in our API contract that a given
closure *must* be called at some point after the function is called.

- Standard API calls like dispatch_async that contractually promise to
execute a closure or block get @required added to their signatures.

- When the compiler sees a @required closure in a function
declaration, it checks to make sure that every execution path either calls
the closure at some point, or sends a @required closure to another API that
eventually ends up calling the closure.

- If there’s a way for a @required closure not to be called, the
compiler emits an error letting the developer know about the bug in his/her
code.

IMPACT ON EXISTING CODE:

None. This is purely additive.

ALTERNATIVES CONSIDERED:

I got nothin’.

Charles

_______________________________________________
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


(Charles Srstka) #18

Quick-n-dirty example, written in Mail. How would you make this synchronous?

import Foundation

enum Result<T> {
  case success(T)
  case error(ErrorType)
}

enum MyError: ErrorType {
  case unknownError
  case corruptData
  case badStatusCode(Int)
}

struct SomeThing {
  init?(data: NSData) {
    ...
  }
}

func getSomethingFromTheNetwork(url: NSURL, completionHandler: (Result<SomeThing>) -> ()) {
  let task = NSURLSession.sharedSession().dataTaskWithURL(url) { data, response, error in
    if let error = error {
      completionHandler(.error(error))
      return
    }

    if let httpResponse = response as? NSHTTPURLResponse {
      let statusCode = httpResponse.statusCode
      
      if statusCode < 200 || statusCode >= 300 {
        completionHandler(.error(MyError.badStatusCode(statusCode)))
        return
      }
    }
  
    guard let data = data else {
      completionHandler(.error(MyError.unknownError))
      return
    }

    guard let something = SomeThing(data: data) else {
      completionHandler(.error(MyError.corruptData))
      return
    }

    completionHandler(.success(something))
  }
  
  task.resume()
}

(disclaimer: yes, we’d probably have to improve the API for NSURLSession a bit here for @required to be useful.)

How would you make this synchronous:

func getSomethingFromAnotherTask(completionHandler: (Result<SomeThing>) -> ()) {
  let message = ...

  xpc_send_message_with_reply(self.connection, message, self.dispatchQueue) { reply in
    do {
      let something = try self.turnReplyIntoSomethingSomehow(reply)
    
      completionHandler(.success(something))
    } catch {
      completionHandler(.error(error))
    }
  }
}

Or this:

func doSomethingThatNeedsUserInput(completionHandler: (Bool) -> ()) {
  let alert = NSAlert()

  alert.messageText = “Should we continue?”
  alert.addButtonWithTitle(“Continue”)
  alert.addButtonWithTitle(“Cancel”)

  alert.beginSheetModalForWindow(someWindow) { response in
    if response == NSAlertFirstButtonReturn {
      completionHandler(true)
    }

    // uh oh, I forgot to test for other conditions, and now the completion handler won’t be called if the user clicked “Cancel”.
    // Too bad the compiler couldn’t warn me about it.
  }
}

There are some tasks which synchronous programming is simply not well-suited for.

Charles

···

On Jun 6, 2016, at 2:49 PM, Michael Peternell <michael.peternell@gmx.at> wrote:

That's really hard to answer in the general case. I think real proposals should contain concrete, realistic examples that show the benefit of the proposal. It's really hard to argue against a proposal if there is no such example. User feedback from a sheet is one of the few examples where asynchronous programming makes sense: But I cannot see how a `@required` annotation would be useful in that setting.


(Michael Peternell) #19

That's really hard to answer in the general case. I think real proposals should contain concrete, realistic examples that show the benefit of the proposal. It's really hard to argue against a proposal if there is no such example. User feedback from a sheet is one of the few examples where asynchronous programming makes sense: But I cannot see how a `@required` annotation would be useful in that setting.

Quick-n-dirty example, written in Mail. How would you make this synchronous?

Yes, ok...

Well, I cannot easily make everything synchronous. I just can change it in a way that makes a `@required` attribute "optional" :wink:

import Foundation

enum Result<T> {
    case success(T)
    case error(ErrorType)
}

enum MyError: ErrorType {
    case unknownError
    case corruptData
    case badStatusCode(Int)
}

struct SomeThing {
    init?(data: NSData) {
        ...
    }
}

func getSomethingFromTheNetwork(url: NSURL, completionHandler: (Result<SomeThing>) -> ()) {
    func toSomeThing(data: NSData?, response: NSURLResponse?, error: NSError?) -> Result<SomeThing> {
        if let error = error {
            return .error(error)
        }

        if let httpResponse = response as? NSHTTPURLResponse {
            let statusCode = httpResponse.statusCode
            
            if statusCode < 200 || statusCode >= 300 {
                return .error(MyError.badStatusCode(statusCode))
            }
        }
    
        guard let data = data else {
            return .error(MyError.unknownError)
        }

        guard let something = SomeThing(data: data) else {
            return .error(MyError.corruptData)
        }

        return .success(something)
    }
    let task = NSURLSession.sharedSession().dataTaskWithURL(url) { data, response, error in
        completionHandler(toSomething(data, response, error))
    }
    
    task.resume()
}

With a semaphore, I can also make it synchronous. Not sure if this is a good idea though... If the API is already asynchronous, it's probably better to use it that way.

/// Should not be called from the main thread
func getSomethingFromTheNetworkSync(url: NSURL) -> Result<SomeThing> {
    func toSomeThing(data: NSData?, response: NSURLResponse?, error: NSError?) -> Result<SomeThing> {
        if let error = error {
            return .error(error)
        }

        if let httpResponse = response as? NSHTTPURLResponse {
            let statusCode = httpResponse.statusCode
            
            if statusCode < 200 || statusCode >= 300 {
                return .error(MyError.badStatusCode(statusCode))
            }
        }
    
        guard let data = data else {
            return .error(MyError.unknownError)
        }

        guard let something = SomeThing(data: data) else {
            return .error(MyError.corruptData)
        }

        return .success(something)
    }
    let sema = dispatch_semaphore_create(0)
    var result = Result<Something>?
    let task = NSURLSession.sharedSession().dataTaskWithURL(url) { data, response, error in
        result = toSomething(data, response, error)
        dispatch_semaphore_signal(sema)
    }
    
    task.resume()
    dispatch_semaphore_wait(sema)
    return result!
}

The other example can be transformed in the same way..

-Michael

···

Am 06.06.2016 um 22:30 schrieb Charles Srstka via swift-evolution <swift-evolution@swift.org>:
On Jun 6, 2016, at 2:49 PM, Michael Peternell <michael.peternell@gmx.at> wrote:

import Foundation

enum Result<T> {
  case success(T)
  case error(ErrorType)
}

enum MyError: ErrorType {
  case unknownError
  case corruptData
  case badStatusCode(Int)
}

struct SomeThing {
  init?(data: NSData) {
    ...
  }
}

func getSomethingFromTheNetwork(url: NSURL, completionHandler: (Result<SomeThing>) -> ()) {
  let task = NSURLSession.sharedSession().dataTaskWithURL(url) { data, response, error in
    if let error = error {
      completionHandler(.error(error))
      return
    }

    if let httpResponse = response as? NSHTTPURLResponse {
      let statusCode = httpResponse.statusCode
      
      if statusCode < 200 || statusCode >= 300 {
        completionHandler(.error(MyError.badStatusCode(statusCode)))
        return
      }
    }
  
    guard let data = data else {
      completionHandler(.error(MyError.unknownError))
      return
    }

    guard let something = SomeThing(data: data) else {
      completionHandler(.error(MyError.corruptData))
      return
    }

    completionHandler(.success(something))
  }
  
  task.resume()
}

(disclaimer: yes, we’d probably have to improve the API for NSURLSession a bit here for @required to be useful.)

How would you make this synchronous:

func getSomethingFromAnotherTask(completionHandler: (Result<SomeThing>) -> ()) {
  let message = ...

  xpc_send_message_with_reply(self.connection, message, self.dispatchQueue) { reply in
    do {
      let something = try self.turnReplyIntoSomethingSomehow(reply)
    
      completionHandler(.success(something))
    } catch {
      completionHandler(.error(error))
    }
  }
}

Or this:

func doSomethingThatNeedsUserInput(completionHandler: (Bool) -> ()) {
  let alert = NSAlert()

  alert.messageText = “Should we continue?”
  alert.addButtonWithTitle(“Continue”)
  alert.addButtonWithTitle(“Cancel”)

  alert.beginSheetModalForWindow(someWindow) { response in
    if response == NSAlertFirstButtonReturn {
      completionHandler(true)
    }

    // uh oh, I forgot to test for other conditions, and now the completion handler won’t be called if the user clicked “Cancel”.
    // Too bad the compiler couldn’t warn me about it.
  }
}

There are some tasks which synchronous programming is simply not well-suited for.

Charles

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


(Matthew Johnson) #20

Thanks Matthew, my responses are inline:

Sent from my iPad

Perhaps I was unclear, in my explanation. The guarantee I'm enforcing is that the closure is called exactly once before being released.

Everything I suggested is a compile-time check.

The compile-time warning and runtime `fatalError` I suggested could be replaced with a compile-time error, however even in this case it is still statically checked for the warning.

The compiler can statically guarantee exactly one of these things happens in methods using the closure:
the closure is called
the closure is stored
the closure is passed to another method
the program aborts with something like a fatalError
If the closure is stored then there must be a deinit, and those checks apply there as well.

I believe this is sufficient to ensure the closure is called once. Please let me know if there are any cases these checks miss.

If the closure is stored in a member it could be called in the implementation of any other member. Calls to other members could come from arbitrary locations in the surrounding program at arbitrary points in time (unless you have static analysis that can prove a narrower set of possibilities).

This isn't a problem if the member has to have the type annotation, all uses of the member, whether from elsewhere in the program, or other methods, would have to pass the checks.

If you call the closure you must nil/replace the member.

Ok, so the member must be an optional? If that is the idea I would suggest considering something similar to 'weak' where it automatically gets set to nil after it is called (which could maybe become a property behavior in the future).

I don't think you mentioned the case of reassigning the member when it is non-nil. You would have to require users to verify it is nil before setting it or if it is not nil, calling it before assigning to it.

And if you have a model that relies on behavior in a deinit then storing the closure won't be possible for structs.

This is true, considering you don't want to copy a @once closure you probably don't want value-type semantics anyway.

That's a good point. Since we don't have control over copy behavior in Swift it wouldn't make sense at all unless / until we can make structs that have move semantics (maybe if / when we get a Rust-like ownership system?).

You have also missed the case that the closure is captured by another closure (maybe it is a completion block and you call it in a completion block of a method your method calls).

This is correct. I forgot to mention that I'm sorry, thanks for pointing it out!

I was thinking that a @once closure can only be captured by another @once closure. We can add that as another dot-point:

the closure is captured by another @once closure, this is the only time it can be captured.

I like the idea behind this proposal in theory. However, it really seems to cry out for linear types. I have a feeling we would end up with a better (and more general) solution if Swift goes down that path in the future. At minimum, I would like to hear input from those who work on the type system. If a more robust, less ad-hoc solution will be possible in the future it might be best to wait.

On the other hand, completion callbacks that require this guarantee are pretty common. The semantic is part of the contract whether we have language support for it or not. Maybe we can do something now that could be subsumed by a more general feature in the future...

···

Sent from my iPad

On Jun 5, 2016, at 8:43 PM, Andrew Bennett <cacoyi@gmail.com> wrote:

On Mon, Jun 6, 2016 at 10:32 AM, Matthew Johnson <matthew@anandabits.com> wrote:

On Jun 5, 2016, at 6:50 PM, Andrew Bennett <cacoyi@gmail.com> wrote:

On Sun, Jun 5, 2016 at 11:59 PM, Matthew Johnson <matthew@anandabits.com> wrote:

Sent from my iPad

On Jun 5, 2016, at 8:52 AM, Andrew Bennett <cacoyi@gmail.com> wrote:

Storing into a member would be fine, as long as it must keep @once as a type annotation and the compiler makes sure you maintain:
    sum(callCount, storeCount, passCount) == 1

For example:
  class Example {
    private var closure: (@once (T) -> Void)?

    func callClosure(value: T, replace: (@once (T) -> Void)? = nil) {
      // the compiler should error if it detects the closure:
      // * escaping more than once, while still being stored,
      // * or being called while still being stored or escaping,
      // * or being overwritten without being called
      if let closure = self.closure {
        self.closure = replace
        closure(value)
      }
    }

    deinit {
      // compiler warning: that closure is potentially un-called
      // runtime fatalError if it's .Some(Closure) after deinit
    }
  }

There could be a standard library type with those guarantees built in.

I don't consider this compiler verification. It is runtime verification. The best the compiler can do is enforce constraints that allow for guaranteed runtime verification. You can argue that is better than nothing but it is not a static guarantee of correct behavior.

On Sun, Jun 5, 2016 at 10:12 PM, Matthew Johnson <matthew@anandabits.com> wrote:

Sent from my iPad

On Jun 5, 2016, at 6:56 AM, Andrew Bennett <cacoyi@gmail.com> wrote:

I like this.

One of the suggestions on @noescape(once) was that it just becomes @once and works with escaping closures too. It might be possible if compile time checks verified that the closure isn't copied, and that it is called before being deinit-ialized. Failing that I'm happy with a runtime circumstance in the cases the compiler can't check.

Yeah, maybe if it is only used asynchronously and never stored in a member or global it could be verified and that is a pretty common case. That would certainly be easier than the general case.

I prefer @once over @required if the guarantee is single execution. If the guarantee is *at least once* obviously @once is not the right attribute, but I'm not convinced @required is either. Maybe @invoked.

It would be great if @required took into the account the feedback from that proposal and considered the synchronous case too.

As an aside, you can get some of the guarantees you want like this:

func doSomething(completionHandler: (SomeEnum) -> ()) {
  dispatch_async(someQueue) {
    let result: SomeEnum
    // the compiler ensures 'result' is set
    defer { completionHandler(result) }

    if aCondition {
      if bCondition {
        result = .Foo
      } else {
        result = .Bar
      }
      // the compiler ensures you do this, because it is 'let'
      return
    }

    if cCondition {
      result = .Baz
    }
  }
}

On Sun, Jun 5, 2016 at 9:42 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

Sent from my iPad

On Jun 5, 2016, at 5:02 AM, Patrick Pijnappel via swift-evolution <swift-evolution@swift.org> wrote:

This has actually been proposed before, see SE-0073: https://github.com/apple/swift-evolution/blob/master/proposals/0073-noescape-once.md

Actually that proposal was for noescape closures and this suggestion is for escaping closures. I don't think the compiler can verify this for noescape closures. If it is possible it would be far more complicated.

On Sun, Jun 5, 2016 at 11:37 AM, Charles Srstka via swift-evolution <swift-evolution@swift.org> wrote:
MOTIVATION:

As per the current situation, there is a pitfall when writing asynchronous APIs that does not occur when writing synchronous APIs. Consider the following synchronous API:

func doSomething() -> SomeEnum {
        if aCondition {
                if bCondition {
                        return .Foo
                } else {
                        return .Bar
                }
        } else {
                if cCondition {
                        return .Baz
                }
        }
}

The compiler will give an error here, since if both aCondition and cCondition are false, the function will not return anything.

However, consider the equivalent async API:

func doSomething(completionHandler: (SomeEnum) -> ()) {
        dispatch_async(someQueue) {
                if aCondition {
                        if bCondition {
                                completionHandler(.Foo)
                        } else {
                                completionHandler(.Bar)
                        }
                } else {
                        if cCondition {
                                completionHandler(.Baz)
                        }
                }
        }
}

Whoops, now the function can return without ever firing its completion handler, and the problem might not be discovered until runtime (and, depending on the complexity of the function, may be hard to find).

PROPOSED SOLUTION:

Add a @required attribute that can be applied to closure arguments. This attribute simply states that the given closure will always be eventually called, and the compiler can enforce this.

DETAILED DESIGN:

- The @required attribute states in our API contract that a given closure *must* be called at some point after the function is called.

- Standard API calls like dispatch_async that contractually promise to execute a closure or block get @required added to their signatures.

- When the compiler sees a @required closure in a function declaration, it checks to make sure that every execution path either calls the closure at some point, or sends a @required closure to another API that eventually ends up calling the closure.

- If there’s a way for a @required closure not to be called, the compiler emits an error letting the developer know about the bug in his/her code.

IMPACT ON EXISTING CODE:

None. This is purely additive.

ALTERNATIVES CONSIDERED:

I got nothin’.

Charles

_______________________________________________
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