Search code examples
swiftinoutmemory-safety

Memory safety of “+=” operator in Swift


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?


Solution

  • 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.