How to initialize array of class instances using a buffer of uninitialised memory?

How can I create an Array that holds reference type objects using the Array(unsafeUninitializedCapacity:initializingWith:) initializer?

I tried this sample code:

import Foundation
let mock = NSNumber(integerLiteral: 6)

let count = 10
let array = Array<NSNumber>(unsafeUninitializedCapacity: count) { (buffer, initCount) in
	for index in 0..<count {
		buffer[index] = mock
	}
	initCount = count
}
print(array)

But it crashes my Xcode 99% of the time. When I run this with debugging enabled, I get the following error:

Thread 1: EXC_BAD_ACCESS (code=1, address=0x20)

When I run the same code but disable "Debug executable" in Xcode, it works fine.
What am I doing wrong or how can I prevent these BAD_ACCESS errors while debugging?

buffer[index] assumes that the memory was initialized, and whatever was there already needs to be uninitialized.

Try using buffer.initialize(repeating: mock), or buffer.initialize(from: <#T##Sequence#>)

1 Like

Here are a few good videos for when you’re dealing with unsafe APIs:

1 Like

mock is just a placeholder to simplify my code. In reality I want to convert a linked list to an array, so I think I cannot use buffer.initialize(repeating: mock). And since my linked list is linked from end to start I think I cannot use buffer.initialize(from:) either.

What do you mean by:

and whatever was there already needs to be uninitialized

Every element in buffer starts with uninitialized state. The subscript requires that the element you’re pointing is in initialized state, hence the crash (I’m still worried about that 1%).

You need to initialize each element with the pointer APIs. initialize(from:) is one of them. You can also extract buffer.baseAddress and use initialize(to:) and/or its siblings.

Note also that init(unsafeUninitializedCapacity:initializingWith:) requires the first count elements to be initialized, and the rest to be uninitialized (or the Element is trivial type) when the closure returns.

1 Like

Thanks for your quick and concise answer :pray:!
When I change buffer[index] = mock to

 buffer.baseAddress?.advanced(by: index).initialize(to: mock)

That seems to work.


How come we need to manually initialise these addresses when you assign reference types (NSNumber) and not when you assign value types (Int)? For example why does this work in contrast to the NSNumber example?

let count = 10
let array = Array<Int>(unsafeUninitializedCapacity: count) { (buffer, initCount) in
	for index in 0..<count {
		buffer[index] = index
	}
	initCount = count
}
print(array)

ARC is probably one of the main reason. Since trivial types are those that have no reference all the way through.

Most pointer APIs also ignore the un/initialized states when dealing with trivial types, though you should properly check for the ones you use.

1 Like

One minor thing, baseAddress won't be nil if the count isn't zero. You can force unwrap here.

2 Likes

Do not mistake what you need to do for what works. You always need to initialize the memory. It just so happens that with trivial types nothing bad happens if you assume it’s initialized because the compiler isn’t doing anything wrong. But your program should not rely on that behaviour.

1 Like

Thanks for your answer.

@lukasa do you mean that we also have to use the initialize(to:) to assign trivial types like Ints?

Because when I looked at the first code sample in the proposal, I saw no initialize(to:). That causes a bit confusion. Maybe we can improve the docs a bit on how or when to initialize?

Yeah, that’s unfortunate in the proposal.

The documentation is actually pretty consistent: it says you must call an initializing method. This particular pitch, however, relied on more subtle knowledge of Swift to note that in practice you can avoid trouble when using trivial types.

The first order answer is to always initialize memory. The second order answer is that, if you know your type is trivial you can skip that and just use subscript assignment. But if you always do the first, you won’t get burned.

2 Likes

Most APIs do explicitly guarantee that they work fine with trivial type, though. They tend to have things like

Pointee must be un/initialized or Pointee must be a trivial type.

The only documents missing would be whether it's ok to pass uninitialized trivial into a safe context. But yea, just initialize them.