[swift-evolution] Guaranteed closure execution
Félix Cloutier
felixcca at yahoo.ca
Sun Jan 31 21:39:56 CST 2016
To simplify my explanations, let's have these functions:
> func foo() {
> let bar: Int
> withNoEscape { bar = 1 }
> }
>
> func withNoEscape(@autoclosure(once) closure: () -> ()) { /* snip */ }
Looking back, I do think that there should be a way to exit from `withNoEscape` without calling the closure, so yes, throwing should imply that the closure wasn't executed. If it's possible that `foo` swallowed an error from a throwing `withNoEscape`, the compiler should assume that the variables within haven't been initialized:
> func foo() {
> let bar: Int
> do {
> try withNoEscape { bar = 1 }
> // bar has been initialized
> }
> catch {
> // bar has not been initialized
> }
> }
>
> func foo() {
> let bar: Int
> try? withNoEscape { bar = 1 }
> // bar has not been initialized
> }
>
> func foo() {
> let bar: Int
> try! withNoEscape { bar = 1 }
> // bar has been initialized
> }
Closures that can throw are harder since normal functions are allowed to catch an error from a closure. For a @noescape(once) closure, it has been partially executed only and it would be non-trivial to determine what has been executed and what hasn't. As I see it, the best solution would be to force any code path in `withNoEscape` where it catches an exception from the closure to throw to indicate failure (even if the closure partially ran).
Another solution is to prevent `withNoEscape` from throwing, and prevent `@noescape(once)` closures from throwing. It's less classy, but the current places in the standard library where it would be nice to have it do not throw and I have a hunch that most cases won't need to throw either. Recovering from throwing closures does add a lot of complexity that I'm not sure the feature will pay for in the long run.
Félix
> Le 31 janv. 2016 à 12:54:27, Dany St-Amant via swift-evolution <swift-evolution at swift.org> a écrit :
>
>>
>> Le 31 janv. 2016 à 01:17, Félix Cloutier via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> a écrit :
>>
>> I'd like to pitch this proposal to implement the feature: https://github.com/zneak/swift-evolution/blob/master/proposals/00xx-noescape-once.md <https://github.com/zneak/swift-evolution/blob/master/proposals/00xx-noescape-once.md>
>>
>> Rationale for some points:
>>
>>> Only one closure parameter can be marked as @noescape(once) in a function signature.
>>
>>
>> The attribute doesn't specify the order of execution of the closures, so it could have unintended consequences if closure B depends on closure A but closure B is called first. Given the typical use case (the @noescape(once) closure as a trailing closure), I don't think that it's worth it to invest a lot of effort into coming up with a model to decide (and enforce) which closure has to be called first and what to do if either closure throws or something.
>>
>>> it is not required to be executed on a code path that throws;
>>
>>
>> It may need to be clarified into "must" or "must not", but I can't think about very good examples supporting either case right now.
>>
>
> What is the implication of this @noescape(once) closure not being call on throws when the caller use try? variation? Looks like the compiler will have to assume that the @escape(once) is both not called and not not called (sorry for the double-negative). For the try!, i think that the compiler could assume that the @escape(none) is called, as the process would crash otherwise anyway on the throw.
>
> var bad: Bool = true
> enum e: ErrorType {
> case Simple
> }
> func f(@noescape closure: () -> ()) throws {
> if (bad) { throw e.Simple }
> closure()
> }
> let x: Int // Not initialized
> try? f { x = 1 }
> print(x) // May still be uninitialized, compiler must generate error
> x = 2 // May have been initialized, compiler must generate error
>
> This should probably be highlighted in the proposal as an intended limitation for the typical usage.
> Another special case with try? that people may be unlikely to use, is:
>
> func g(@noescape closure: () -> ()) throws -> Int {
> if (bad) { throw e.Simple }
> closure()
> return 1
> }
> if let data = try? g({ x = 1 }) {
> print(x) // Guaranteed to be initialized
> }
> print(x) // May still be uninitialized, compiler must generate error
> x = 2 // May have been initialized, compiler must generate error
>
> Not sure if this case will make the implementation more complex, it is why I’m mentioning it.
>
> Dany
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160131/10d7db42/attachment.html>
More information about the swift-evolution
mailing list