[Review] SE-0073: Marking closures as executing exactly once

I just have two messages by Chris Lattner who did not seem to foresee any big trouble. Of course this does not mean there aren't any, even maybe some pretty difficult one. The second message addresses more precisely your memory handling concerns:

- [swift-evolution] Guaranteed closure execution
- [swift-evolution] Guaranteed closure execution

Gwendal Roué

···

Le 5 mai 2016 à 18:16, Dmitri Gribenko <gribozavr@gmail.com> a écrit :

On Thu, May 5, 2016 at 3:27 AM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

I quite expect being able to throw out of a @noescape(once) block. Maybe the sentence "it must not be executed on any path that throws" should be removed from the proposal, should it have the implications you describe.

Here is below what I expect this proposal to allow. So you see one problematic case?

Hi Gwendal,

What about the following case?

// Function which rethrows closure errors:
func f1(closure: @noescape(once) () throws -> ()) rethrows {
try closure()
}

let x: AnyObject
f1 {
if someCondition() { x = MyClass() }
if someOtherCondition() { throw MyError.Error() }
x = MyOtherClass()
}

How do you handle memory management for 'x' on the path that throws?
If the rule is that upon returning from f1 via a throw the variable
'x' should not be initialized, then the closure passed to f1 has to
guarantee the deinitialization. But f1 accepts an arbitrary closure.

Hello Dmitri,

To reason about @noescape(once) functions, the easiest way is to replace them with `do`:

   let x: AnyObject
   do {
       if someCondition() { x = MyClass() }
       if someOtherCondition() { throw MyError.error }
       x = MyOtherClass()
   }

This code does not compile because x can't be initialized to MyOtherClass().

But I don't think this is your point. Your point was "how can the compiler handle memory management ?".

I can't answer this question, because I'm not competent enough. But if it can handle the do { … } case, can't it also handle the f { … } case?

The difference is that the do{} case is type checked and codegen'ed
together with the rest of the function. The f{} case has two
functions that are type checked and codegen'ed more or less
separately (the function that contains the call to f, and the closure
itself). Moreover, with do{} the placement of the code block is
fixed. With f{}, you can save a closure in a variable and then call
f(myClosure). How would that affect the rules? How would you
implement the desired analysis?

Dmitri

Replies inline:

> Hi Dave,
>
> Sorry, Dave, sending a second time as I forgot to Reply-All.
>
> I agree, this proposal doesn't allow multiple closures where only one of them
> should be run, and it should only be run once. I personally don't think lacking
> that functionality is worth blocking this proposal for, another proposal can be
> built on top of this if it is desired.
>
> These cases can also be handled by a more meaningful if/switch statement, using
> @noescape(once), for example:
> let x: Int
> functionThatCallsAClosure(someTest()) { x = $0 ? 1 : 2 }

Why is this better than

    let x = functionThatCallsAClosure(someTest()) { $0 ? 1 : 2 }

?

I'm not saying it's better, neither is the proposal. I do think both are better than this though:

    functionThatActsLikeIf( someTest(), then: { x = 1 }, else: { x = 2} )

My opinion is that cases where the proposal are limited by multiple closures seem to be cases where you would be better off with a single closure and more explicit control-flow. I'd be interested if there are other cases, but it currently seems like a straw-man argument to me.

--

It may be useful if Swift allowed things like this:

let x = switch { ... }
let x = if { ... } else { ... }
etc.

I think that's a much larger change/discussion, with no clear victor.

However until Swift has that support it's necessary to consider separated initialization and declaration. Likewise until all Swift is pure functional.

Even if this proposal didn't let you assign to let statements outside the closure it still has value:
It lets the type system reduce programmer error
It allows protocol declarations to have a more explicit requirement
The user can guarantee that their code, and its side-effects, will be executed

+1.

···

Sent from my iPad

On May 6, 2016, at 10:10 PM, Andrew Bennett via swift-evolution <swift-evolution@swift.org> wrote:

On Sat, May 7, 2016 at 12:37 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:
on Fri May 06 2016, Andrew Bennett <swift-evolution@swift.org> wrote:

IMO separating initialization from declaration is *very* rarely needed
and very much better avoided altogether, because it leads to code that's
less clear. Just because we *can* do this doesn't mean we should.

> On Sat, May 7, 2016 at 6:24 AM, Dave Abrahams via swift-evolution >> > <swift-evolution@swift.org> wrote:
>
> on Tue May 03 2016, Chris Lattner >> > <swift-evolution@swift.org> wrote:
>
> > Hello Swift community,
> >
> > The review of "SE-0073: Marking closures as executing exactly once"
> > begins now and runs through May 9. The proposal is available here:
> >
> >
> https://github.com/apple/swift-evolution/blob/master/proposals/0073-noescape-once.md
>
> >
> > Reviews are an important part of the Swift evolution process. All reviews
> should be sent to the swift-evolution mailing list at
> >
> > https://lists.swift.org/mailman/listinfo/swift-evolution
> >
> > or, if you would like to keep your feedback private, directly to the
> review manager.
> >
> > What goes into a review?
> >
> > The goal of the review process is to improve the proposal under review
> > through constructive criticism and contribute to the direction of
> > Swift. When writing your review, here are some questions you might
> > want to answer in your review:
> >
> > * What is your evaluation of the proposal?
>
> I think it's of questionable importance and doesn't generalize well.
> For example, you can't use this to construct something like
>
> var x: Int
> functionThatActsLikeIf( someTest(), then: { x = 1 }, else: { x = 2} )
>
> If you need to initialize something in an outer scope with something
> computed by a closure, it's much better to arrange something like this:
>
> var x = functionThatActsLikeIf( someTest(), then: { 1 }, else: { 2 } )
>
> --
> Dave
>
> _______________________________________________
> 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

--
Dave

_______________________________________________
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