> 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