Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

If var seems to deep copy arrays in Swift. Does if let?

In Swift 3.0, the code below gives different addresses for thisArray[0], suggesting that the array was deep copied. Is this actually the case, or am I missing something in my analysis? Does if let behave the same way? It may be irrelevant for if let, as it is immutable...

var thisArray: [String]? = ["One", "Two"]
withUnsafePointer(to: &thisArray![0]) {
    print("thisArray[0] has address \($0)")
}
if var thisArray = thisArray {
    withUnsafePointer(to: &thisArray[0]) {
        print("thisArray[0] has address \($0)")
    }
}
like image 451
diatrevolo Avatar asked Sep 15 '16 17:09

diatrevolo


2 Answers

Relevant: https://developer.apple.com/swift/blog/?id=10.

In Swift, Array, String, and Dictionary are all value types.

So, if you assign an existing value type via var or let then a copy occurs. If you assign an existing reference type (such as a class) via var or let then you'll be assigning a reference.

like image 69
Charlie Schliesser Avatar answered Sep 28 '22 08:09

Charlie Schliesser


@CharlieS's answer is mostly correct but glosses over some important details...

Semantically, assigning a value type to a different binding (whether a var variable or let constant) always creates a copy. That is, your program code can always safely assume that modifications to one binding of a value type will never affect others.

Or to put it a different way: if you were building your own version of the Swift compiler / runtime / standard library from scratch, you could make every var a = b allocate new memory for a and copy all the memory contents of b, regardless of which value type a and b are. All other things being equal, your implementation would be compatible with all Swift programs.

The downside to value type reassignment always being a copy is that for large types (like collections or composite types), all that copying wastes time and memory. So...

In practice, value types can be implemented in ways that maintain the semantic always-a-copy guarantee of value types while providing performance optimizations like copy-on-write. The Swift Standard Library collection types (arrays, dictionaries, sets, etc) do this, and it's possible for custom value types (including yours) to implement copy-on-write too. (For details on how, this WWDC 2015 talk provides a good overview.)

To make copy-on-write work, an implementing value type needs to use reference types internally (as noted in that WWDC talk). And it has to do it in such a way that the language guarantee for value types — that assignments are always semantically copies — continues to hold in all cases.


One of the ways that a copy-on-write array implementation could fail that guarantee would be to allow unguarded access to its underlying storage buffer — if you can get a raw pointer into that storage, you could mutate the contents in ways that cause other bindings (that is, semantic copies) to mutate, violating the language guarantee.

To preserve the copy-on-write guarantee, the standard library's collection types make sure that copies certain operations that could perform unguarded mutation create copies. (Although even then, sometimes the copies created involve enough reference manipulation that the memory and time costs of the copies remain low up until an actual mutation happens.)

You can see a bit of how this works in the Swift compiler & standard library source code — start from a search for isUniquelyReferenced and follow the callers and callees of is various use cases in ArrayBuffer etc.


For an illustration of what's going on here, let's try a variation on your test:

var thisArray: [String] = ["One", "Two"]
withUnsafePointer(to: &thisArray[0]) {
    print("thisArray[0] has address \($0)")
}
var thatArray = thisArray // comment/uncomment here
withUnsafePointer(to: &thisArray[0]) {
    print("thisArray[0] has address \($0)")
}

When you comment out the assignment thatArray = thisArray, both addresses are the same. Once thisArray is no longer uniquely referenced, though, accessing even the original array's underlying buffer requires a copy (or at least some internal indirection).

like image 32
rickster Avatar answered Sep 28 '22 09:09

rickster