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 <[email protected]> wrote: > > > Sent from my iPad > > On Jun 5, 2016, at 8:52 AM, Andrew Bennett <[email protected]> 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 <[email protected]> > wrote: > >> >> >> Sent from my iPad >> >> On Jun 5, 2016, at 6:56 AM, Andrew Bennett <[email protected]> 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 < >> [email protected]> wrote: >> >>> >>> >>> Sent from my iPad >>> >>> On Jun 5, 2016, at 5:02 AM, Patrick Pijnappel via swift-evolution < >>> [email protected]> 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 < >>> [email protected]> 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 >>>> [email protected] >>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>> >>> >>> _______________________________________________ >>> swift-evolution mailing list >>> [email protected] >>> https://lists.swift.org/mailman/listinfo/swift-evolution >>> >>> >>> _______________________________________________ >>> swift-evolution mailing list >>> [email protected] >>> https://lists.swift.org/mailman/listinfo/swift-evolution >>> >>> >> >
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
