[swift-evolution] Pitch: @required attribute for closures

Andrew Bennett cacoyi at gmail.com
Sun Jun 5 08:52:21 CDT 2016


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 at anandabits.com>
wrote:

>
>
> Sent from my iPad
>
> On Jun 5, 2016, at 6:56 AM, Andrew Bennett <cacoyi at 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 at swift.org> wrote:
>
>>
>>
>> Sent from my iPad
>>
>> On Jun 5, 2016, at 5:02 AM, Patrick Pijnappel via swift-evolution <
>> swift-evolution at 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 at 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 at swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160605/39de521d/attachment.html>


More information about the swift-evolution mailing list