I'm trying to mutate the items in a Set: (contrived example, the real code is more complex with a set of structs with a mutating function)
var items: Set = ["1", "2", "3"]
for index in items.indices {
items[index] += "1"
}
print(items)
This provides the error Left side of mutating operator isn't mutable: subscript is get-only
If I change items: Set to items: Array then the code works fine.
Ideally I wouldn't have to use indices but I understand why they have to be used with an array as the for loop provides a copy of the item, so I imagined it would be the same with Set even though a Set doesn't have numeric indices.
This loop is guaranteed to duplicate the entire storage of the set, regardless of whether any duplicate entries occur. As soon as you mutate the set, copy-on-write gets triggered because there are two references to items.
Specifically, the line “for item in items” creates an (implicit, shallow, semantic) copy of items which is only ever used for the purpose of iteration. Then at the point of mutation, the existence of a second reference leads to CoW duplicating the storage.
The same thing happens if you loop over items.indices instead. I do not know of a way to reuse the storage and avoid a duplicate allocation when mutating all the elements of a set
This approach will allocate a separate array as well as a new set.
This is better because it does not introduce an extraneous array allocation, it only allocates the new set.
This is probably fine as well, but I’m always a little leery about things like .lazy.map because it can be hard to verify that the compiler really did choose the lazy version of map and not the one that returns an array. I’ve seen situations where laziness was silently and invisibly dropped for reasons that are very much not obvious at a glance.