Why does Array.init(repeating:count:) refer to the same instance of a struct?

The documentation for Array.init(repeating:count:) says (emphasis mine):

Creates a new array containing the specified number of a single, repeated value.

Structs are value types, hence it seems reasonable to expect that passing an instance of a struct to repeating: creates as many instances of the struct as count specifies.

However, as in the code below, that's not the case, and printing out the addresses of elements reveals that the array somehow contains the same struct instance.

struct MyStruct {  }

let array = Array(repeating: MyStruct(), count: 5)
for var element in array {
  withUnsafePointer(to: &element) {
    print($0) // prints the same address
  }
}

This seems like reasonable behavior for class instances, but not struct instances. I get it that I might be misreading "repeating" as "duplicating", and perhaps the purpose of this initializer is just to assign default values, but I'd like to know whether this is deliberate behavior. Thanks in advance!

withUnsafePointer(to: &element) is not giving you the address of the element in the array; it's giving you an address for the variable element (and that pointer is only valid within the closure argument). That address can be the same each pass through the loop.

The following is closer to what you want:

var array = Array(repeating: MyStruct(), count: 5)
for i in 0..<5 {
  withUnsafePointer(to: &array[i]) { print($0) }
}
0x00000001005040e0
0x00000001005040e1
0x00000001005040e2
0x00000001005040e3
0x00000001005040e4

But note that even in this example, the compiler could still (in theory) produce code that prints the same address on each pass through the loop, because Swift objects in general do not have stable addresses. This is unlikely to happen in this case for a variety of reasons, however.

3 Likes

OK, I misunderstood withUnsafePointer(to:) and incorrectly used it to illustrate my point.

I discovered this behavior while trying to make multiple copies of a struct that holds a class. Printing out the elements of the array shows that the class instances held by the structs have the same address, and are therefore the same instance, therefore the structs themselves must be the same instance.

This code, I think, correctly shows the behavior:

class ImageLoader: NSObject { }

struct MyPresenter {
  let imageLoader = ImageLoader()
}

let presenters = Array(repeating: MyPresenter(), count: 5)
for presenter in presenters {
  // prints MyPresenter(imageLoader: <CommandLineTool.ImageLoader: 0xADDRESS>) count times
  print(presenter)
}

I was using this approach to populate the cells of a collection view.

The struct is a cell presenter (takes a data model, then reads and formats its contents for a cell), and the class member is a utility that loads the cell's image asynchronously.

Items are in an items array, and the presenters are in a same-sized itemPresenters array, which I initialized in the manner above.

Then, I had the bizarre behavior of having the image of the first item appearing in all of my cells, because they've all been modified by the same ImageLoader instance, and I imagine that the only way for the same class instance to appear in the entire itemPresenters array is for the struct instances to be the same as well.

This does not follow; in fact, it doesn't really make sense. Structs are value types, and so do not have identity. There is no notion of "the same instance" for structs.

3 Likes

Structs that have properties to class types share reference semantics. The class property is not copied when a new struct instance is created unless explicitly done by your code.

You need to explicitly initialize an ImageLoader for each instance of the MyPresenter struct so that it isn't shared between the instances.

Think of it this way: the class type property is really "just a pointer", so when you create a new copy of the struct, it will copy the address of the pointer.

2 Likes

Thank you, @scanon and @Mordil.