I'm using Xcode 10 and Playground for test, code is work without error?
Below is the "The Swift Programming Language (Swift 4.2)"
A function has long-term write access to all of its in-out parameters. The write access for an in-out parameter starts after all of the non-in-out parameters have been evaluated and lasts for the entire duration of that function call. If there are multiple in-out parameters, the write accesses start in the same order as the parameters appear.
One consequence of this long-term write access is that you can’t access the original variable that was passed as in-out, even if scoping rules and access control would otherwise permit it—any access to the original creates a conflict. For example:
var stepSize = 1
func increment(_ number: inout Int) {
number += stepSize
}
increment(&stepSize)
// Error: conflicting accesses to stepSize
In your case – if I understand it correctly – it is unsafe to access stepSize inside func increment(), because the same variable is passed as in-out parameter to the function.
I wouldn't call particularly that example unsafe, but yes, your case falls under the first part of the motivation section of SE-0176. There shouldn't be an error in a playground though.
The error is correct. You could say "oh, in this case it's safe because we only read from stepSize before doing any modification of the inout arguments", but that makes an already subtle language rule even more complicated. Instead, we went with a simpler rule: "when a variable is passed inout, it cannot be read from or written to until the function call is over".
Was an exception made for playground top-level stuff, for which the error doesn't appear at runtime in Xcode 10? I don't see how the specific rule you stated could differ for a playground.
I don't see how that is unsafe either. Regardless, I agree with Jordan as long as narrowing the rule involves significant unwanted complications to the compiler.
This rule was not narrowed, it's always been there. Previously, my example above with increment(stepSize) was unspecified behavior. The code is also ambiguous. Anyone who mentally maps inout to pass-by-reference will misunderstand it.
Here's a fun example of code that behaves completely wrong without inout being considered a write for the entire duration of the function:
func increment(_ i: inout Int) {
var totallyACopyNothingToSeeHere = array
totallyACopyNothingToSeeHere[0] += 1
i += 1
}
var array = [1]
increment(&array[0])
print(array) // [3]
Array's subscript pins its buffer for the mutation of &array[0], so when attempting to mutate it again within that window, it modifies in-place rather than re-allocating.
Considering how inout works, that should make stepSize equal to 3, since the value the in-out parameter is assigned isn't passed out of the function until it returns. If it were simply a reference, stepSize would end up being 4. Currently, in an Xcode 10 playground, the result is 4, which brings back the question I addressed to @jrose
@anandabits just to prove that you're not wrong, you can also try this one:
struct S<T: SignedInteger> {
var stepSize: T
func increment(_ x: inout T) {
x += stepSize
x += stepSize
}
}
var s = S(stepSize: 1)
s.increment(&s.stepSize)
print(s.stepSize)
swiftc ./t.swift -enforce-exclusivity=unchecked
(hint: you'll get a different answer, which is what we mean by unspecified behavior).
@Andrew_Trick I was confusing unspecified behavior with UB. What is the main reason behind enforcing such a broad rule instead of fixing the behavior to be specified and consistent with the current language rules where it can be? Note that I agree sometimes access exclusivity is beneficial. For instance, when a value might be or is being simultaneously accessed in a completely different place or thread.
A rule that simply prohibits simultaneous access to the same variable or property cannot be misinterpreted. Once it's fully enforced, it can't be ignored either.
We could make a rule specifying a particular behavior instead, but that would always be counterintuitive to some set of developers and some code patterns. I don't know where you would draw the line with "obvious" behavior.
We do loosen enforcement for struct properties, when two accesses are to obviously different memory locations within the same struct, since there can be no debate about the proper semantics in that case.
@hamishknight posted an example above illustrating why we don't allow this. I also think the trivial example that I posted would be misleading for anyone who interprets Swift's '&' parameter as pass-by-reference.
Exclusivity does not protect threads from accessing the same shared property or global.