I’ve been learning swift and encountered a question about memory safety. The +=
operator takes an inout
parameter on the left, which should have write access over the entire function call. And it do something like left = right+left
within its implementation. It seems to be an overlapping of write and read accesses. How comes this doesn’t violate memory safety?
Edit: According to The Swift Programming Language, it can happen in a single thread:
However, the conflicting access discussed here can happen on a single thread and doesn’t involve concurrent or multi-threaded code.
Elaborate:
Here are two examples from The Swift Programming Language (Swift 4.1 beta). I’m confused how this custom +=
implementation in a struct Vector2D
is okay:
static func += (left: inout Vector2D, right: Vector2D) {
left = left + right
}
When this is not:
var stepSize = 1
func incrementInPlace(_ number: inout Int) {
number += stepSize
}
incrementInPlace(&stepSize)
// Error: conflicting accesses to stepSize
Further edit:
I think my problem really is that += as a func, specifically when used
stepSize += stepSize
Or with custom implementation:
var vector = Vector2D(x: 3.0, y: 1.0)
vector += vector
This doesn’t have any error. But the func takes an inout from the left and thus have a long-term write access to “step”, then if the right also passed in “step”, I’m confused how that isn’t an instant read acess of “step” overlapping with long term write of “step.” Or is it only a problem when you pass in the same instance for two inout parameters, but nor one inout and one regular?
I know you've got it, but a clarification for future readers; in your comments, you said:
... it is, in the end, a problem with any one-line code changing self by reading self first.
No, that alone is not sufficient. As the Memory Safety chapter says, this problem manifests itself only when:
- At least one is a write access.
- They access the same location in memory.
- Their durations overlap.
Consider:
var foo = 41
foo = foo + 1
The foo = foo + 1
is not a problem (nor would foo += 1
; nor would foo += foo
) because constitutes a series of "instantaneous" accesses. So although we have (to use your phrase) "code changing self by reading self first", it is not a problem because their durations do not overlap.
The problem only manifests itself when you're dealing with "long-term" accesses. As that guide goes on to say:
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.
So, consider your second example:
var stepSize = 1
func incrementInPlace(_ number: inout Int) {
number += stepSize
}
incrementInPlace(&stepSize)
In this case, you have a long-term access to whatever number
references. When you invoke it with &stepSize
, that means you have a long-term access to the memory associated with stepSize
and thus number += stepSize
means that you're trying to access stepSize
while you already have a long-term access to it.