This site, What is Copy on Write (CoW) in Swift? | by Gaye Uğur | Medium), suggests that tuples support copy on write. This doesn't sound right to me (I was under the impression that COW is something you have to actively implement yourself [or has been implemented in the StdLib], whereas tuples seem like something baked into the language.) Is it true that tuples support COW? Or is this web page mistaken? (Hey! It's on the Internet, it must be true, right?)
Thanks
Nothing in Swift automatically supports copy on write, it's manually added to type that need it, like the default collection types. CoW types in tuples should still CoW as part of copies of the tuple.
Obviously, if one of the fields is a COW type, that field will be copied-on-write. I'm talking about something simple like
typealias Example = (Int, Int, Int).
var a :Example(1,2,3)
var b = a
Does it immediately copy all of a to b, or will it defer until one of them changes a member? My thought was the former.
In an example this simple the optimizer will likely delete the copy entirely, but assuming it doesn’t, it will eagerly copy rather than on write.
Also note that structs are not guaranteed to be contiguous in memory, so if say, only one field is actually used, the optimizer is allowed to just not store the others at all.
OK, thanks, that's what I wanted to verify.
just to clarify here @David_Smith – did you mean structs or tuples (or both)?
The second code block (and probably the first one but I didn’t check because it’s actually a screenshot) does not compile in Godbolt because you can’t cast an arbitrary value type to AnyObject
. The whole thing smells a bit of ChatGPT to me so I wouldn’t take it too seriously.
Are tuples not guaranteed to be contiguous in memory? While it seems reasonable that you should not expect this, I read so many posts that claim this is one advantage of tuples over structs. I've even found code that provides an index into a tuple (like an array), assuming the tuple has homogeneous types:
struct myArray4s
{
private var ma4Storage: (Int, Int, Int, Int)
init( _ i0:Int, _ i1:Int, _ i2:Int, _ i3:Int )
{
ma4Storage.0 = i0
ma4Storage.1 = i1
ma4Storage.2 = i2
ma4Storage.3 = i3
}
var count :Int
{
return 4
}
subscript( index:Int )->Int
{
mutating get
{
withUnsafeMutablePointer(to: &self.ma4Storage)
{ ma4Ptr in
ma4Ptr.withMemoryRebound(to: Int.self, capacity: 4)
{ ma4Array in
return ma4Array[index]
}
} // end withUnsafeMutablePointer
} // end getter
set( newValue )
{
withUnsafeMutablePointer(to: &ma4Storage)
{ ma4Ptr in
ma4Ptr.withMemoryRebound(to: Int.self, capacity: 4)
{ ma4Array in
ma4Array[index] = newValue
}
} // end withUnsafeMutablePointer
} // end setter
} // end subscript
} // end myArray4s
var my4 = myArray4s( 0, 1, 2, 3 )
print( "Struct version: ", terminator:"" )
for i in 0..<my4.count
{
print( my4[i], terminator:" " )
}
print()
my4[2] = 15
print( "Struct version2: ", terminator:"" )
for i in 0..<my4.count
{
print( my4[i], terminator:" " )
}
print()
print( my4 )
The output from this is:
Struct version: 0 1 2 3
Struct version2: 0 1 15 3
myArray4s(ma4Storage: (0, 1, 15, 3))
Which suggests the tuple elements are stored contiguously in memory. Of course, the fact that this program demonstrates this "feature" today is no guarantee it will continue to work tomorrow; but I am wondering if contiguous storage for tuple is something Swift guarantees, or if this is just a coincidence.
John's point is the relevant bit here:
Tuples are guaranteed to use a standard C-style layout wherever that layout is ABI-observable, e.g. when you construct an UnsafePointer to one.
Calling withUnsafeMutablePointer
causes Swift to materialize a temporary contiguous addressable version, which may or may not exist before or after.
Exactly what David said. If the memory layout is observed by the program, the tuple is guaranteed to be laid out contiguously in memory. If it isn't, then the compiler is free to never build the tuple at all, or keep it in registers, or lay it out in some other manner.
And it's free to construct the contiguous tuple from scratch before the tuple is observed, and decompose it again after the observation is finished.
var elements = (0, 1, 2, 3, 4)
// contiguous storage may or may not exist here.
print(elements.1, elements.3)
withUnsafeMutablePointer(to: elements) { arr in
// contiguous storage exists here
for i in 0..<5 {
(arr + i).pointee *= 2
}
}
// contiguous storage may not exist here.
print(elements.0, elements.2, elements.4)
And the optimizer may still optimize the whole thing into:
print(1, 3)
print(0, 4, 8)
Or anything in between.