> On May 16, 2017, at 5:20 PM, Jordan Rose <[email protected]> wrote:
> 
> 
>> On May 15, 2017, at 20:29, John McCall <[email protected] 
>> <mailto:[email protected]>> wrote:
>> 
>>> 
>>> On May 15, 2017, at 9:00 PM, Jordan Rose <[email protected] 
>>> <mailto:[email protected]>> wrote:
>>> 
>>> [Proposal: 
>>> https://github.com/apple/swift-evolution/blob/master/proposals/0176-enforce-exclusive-access-to-memory.md
>>>  
>>> <https://github.com/apple/swift-evolution/blob/master/proposals/0176-enforce-exclusive-access-to-memory.md>]
>>> 
>>> I have severe concerns about these revisions because they make 
>>> withoutActuallyEscaping harder to reason about.
>>> 
>>> +  - Programmers using ``withoutActuallyEscaping`` should take
>>> +    care not to allow the result to be recursively invoked.
>>> 
>>> +[…] if they are certain that their code will
>>> +not violate the NRR, use ``withoutActuallyEscaping`` to disable
>>> +the NPCR check.
>>> 
>>> I think this constitutes a violation of the user model for 
>>> withoutActuallyEscaping, because withoutActuallyEscaping hasn't 
>>> historically meant withoutBeingReentrant. The proposal "should take care" 
>>> is a very mild way of saying we're introducing a new rule under which the 
>>> compiler can silently do something different than what you wrote (with no 
>>> "unsafe" in sight). Similarly, using withoutActuallyEscaping to mean 
>>> withoutActuallyViolatingExclusivity seems like it'll come back to haunt us, 
>>> the kind of thing where people ask on Stack Overflow why the Swift people 
>>> didn't design something that said what it did.
>> 
>> You're right that it's a silent change in the semantics of 
>> withoutActuallyEscaping.  I'm comfortable with that because of the ways in 
>> which I expect withoutActuallyEscaping to be used, but I can see why someone 
>> wouldn't be.
>> 
>>> When I first brought this up on an Apple-internal list, John let me know 
>>> that we could introduce checking for it. This helps assuage my concerns 
>>> quite a bit, but I'm not sure how we would do so without modifying the 
>>> original closure, and if we can modify the original closure I'm not sure 
>>> we've saved anything over proper dynamic checking.
>> 
>> withoutActuallyEscaping does not actually promise to return exactly the 
>> original closure (which is not a user-detectable property of the closure 
>> value), just something semantically equivalent.  In particular, we can wrap 
>> it in a thunk that (say) dynamically asserts that the closure is not called 
>> re-entrantly.
>> 
>> We may need withoutActuallyEscaping to add a thunk anyway, because I don't 
>> know that we actually want to guarantee that the context pointer of a 
>> non-escaping closure is retainable; it costs us unnecessary set-up code in 
>> the caller.
> 
> I thought of this too, but it doesn't handle the case where you use 
> withoutActuallyEscaping and use the original closure directly from within the 
> block. Maybe we can forbid that, though—it seems extra-rare. Does it make 
> sense to add that as an extra rule in this proposal?

Well, you'd have to integrate the check with some sort of record-keeping done 
by the original closures.  I'm not entirely sure what that record-keeping would 
look like off-hand.

I'm not sure what you're proposing to disallow here.  I don't think we should 
add any extra restrictions in order to avoid problems with unfortunate uses of 
withoutActuallyEscaping, though.

John.

> 
> 
>> 
>>> John, can you clarify for the list how we might check for this? Like 
>>> withoutActuallyEscaping, it doesn't have to be something we implement right 
>>> away as long as we have the ability to do so without changing the ABI.
>>> 
>>> Thanks,
>>> Jordan
>>> 
>>> P.S. Devin and I had previously discussed an additional example that does 
>>> not seem to be forbidden by these rules. Is that correct and will this 
>>> program continue to print "2 2"?
>>> 
>>> func invoke(_ callback: /*nonescaping*/ () -> Void) {
>>>   callback()
>>> }
>>> class Foo {
>>>   var op: () -> Void = {}
>>>   var prop = 0
>>>   func test() {
>>>     var x = 0
>>>     self.op = { x = 1; self.prop = 1 }
>>>     invoke { self.op(); x += 1; self.prop += 1 }
>>>     print(x, self.prop)
>>>   }
>>> }
>>> Foo().test()
>> 
>> Correct.  The closure passed to 'invoke' does recurse into a closure that 
>> captures the same variable, but that closure is not non-escaping, so the NRR 
>> is not violated.
>> 
>> On a design-intent level, recursing into an escaping closure is fine because 
>> any variable captured in an escaping closure is escaping, and therefore the 
>> accesses to it are generally not statically analyzable and must use dynamic 
>> enforcement.
>> 
>> (I did just now notice the phrasing of the NRR in the proposal isn't clear 
>> about the restriction only forbidding *indirect* recursion, but your example 
>> doesn't violate the stronger rule either.  This can be fixed later because 
>> it's just a weakening.)
> 
> Thanks for the clarification!
> Jordan
> 

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to