If you want a fixed-size array in Swift, then you have two options. (Edit: as Nevin notes below, there are other options. This post only features two, though.)
-
The withUnsafeTemporaryAllocation
functions (documentation here and here).
These functions temporarily allocate an arbitrary amount of memory for the duration of a closure. These functions prefer stack allocation, though if you allocate enough memory (over 1024 bytes currently, though this is subject to change) it'll allocate on the heap to prevent stack overflow with large buffers.
-
Using a tuple where each element is the same type (e.g. (Float, Float, Float, Float)
). Tuples can be allocated on the stack. They are guaranteed to be stored contiguously (see Safely manage pointers in Swift - WWDC20 - Videos - Apple Developer), so you can randomly access tuple elements by taking a pointer to the tuple and rebinding that pointer to the element type. Here's an example of a generic four-element array structure:
struct CollectionOfFour<Element> {
var elements: (Element, Element, Element, Element)
static var count: Int { 4 }
init(_ element0: Element, _ element1: Element, _ element2: Element, _ element3: Element) {
elements = (element0, element1, element2, element3)
}
func withUnsafePointerToElements<R>(_ body: (UnsafePointer<Element>) throws -> R) rethrows -> R {
return try withUnsafePointer(to: elements) {
return try $0.withMemoryRebound(to: Element.self, capacity: Self.count, body)
}
}
mutating func withUnsafeMutablePointerToElements<R>(_ body: (UnsafeMutablePointer<Element>) throws -> R) rethrows -> R {
return try withUnsafeMutablePointer(to: &elements) {
return try $0.withMemoryRebound(to: Element.self, capacity: Self.count, body)
}
}
subscript(index: Int) -> Element {
get {
precondition((0..<Self.count).contains(index), "Index out of range")
return withUnsafePointerToElements {
return $0[index]
}
}
set {
precondition((0..<Self.count).contains(index), "Index out of range")
withUnsafeMutablePointerToElements {
$0[index] = newValue
}
}
}
// You can use this method to avoid potential redundant range checks and avoid ARC/copy-on-write issues.
mutating func withElement<R>(_ index: Int, _ body: (inout Element) throws -> R) rethrows -> R {
precondition((0..<Self.count).contains(index), "Index out of range")
return try withUnsafeMutablePointerToElements {
return try body(&$0[index])
}
}
}
More code: conforming `CollectionOfFour` to `RandomAccessCollection` and `MutableCollection`
struct CollectionOfFour<Element> {
var elements: (Element, Element, Element, Element)
static var count: Int { 4 }
init(_ element0: Element, _ element1: Element, _ element2: Element, _ element3: Element) {
elements = (element0, element1, element2, element3)
}
func withUnsafePointerToElements<R>(_ body: (UnsafePointer<Element>) throws -> R) rethrows -> R {
return try withUnsafePointer(to: elements) {
return try $0.withMemoryRebound(to: Element.self, capacity: Self.count, body)
}
}
mutating func withUnsafeMutablePointerToElements<R>(_ body: (UnsafeMutablePointer<Element>) throws -> R) rethrows -> R {
return try withUnsafeMutablePointer(to: &elements) {
return try $0.withMemoryRebound(to: Element.self, capacity: Self.count, body)
}
}
mutating func withElement<R>(_ index: Int, _ body: (inout Element) throws -> R) rethrows -> R {
precondition((0..<Self.count).contains(index), "Index out of range")
return try withUnsafeMutablePointerToElements {
return try body(&$0[index])
}
}
}
extension CollectionOfFour: RandomAccessCollection, MutableCollection {
var startIndex: Int { 0 }
var endIndex: Int { Self.count }
subscript(index: Int) -> Element {
get {
precondition((0..<Self.count).contains(index), "Index out of range")
return withUnsafePointerToElements {
return $0[index]
}
}
set {
precondition((0..<Self.count).contains(index), "Index out of range")
withUnsafeMutablePointerToElements {
$0[index] = newValue
}
}
}
func index(before i: Int) -> Int {
return i - 1
}
func index(after i: Int) -> Int {
return i + 1
}
func withContiguousStorageIfAvailable<R>(_ body: (UnsafeBufferPointer<Element>) throws -> R) rethrows -> R? {
return try withUnsafePointerToElements {
return try body(UnsafeBufferPointer(start: $0, count: Self.count))
}
}
mutating func withContiguousMutableStorageIfAvailable<R>(_ body: (inout UnsafeMutableBufferPointer<Element>) throws -> R) rethrows -> R? {
return try withUnsafeMutablePointerToElements { baseAddress in
var buffer = UnsafeMutableBufferPointer(start: baseAddress, count: Self.count)
defer { // buffer should not be changed within body
assert(buffer.baseAddress == baseAddress)
assert(buffer.count == Self.count)
}
return try body(&buffer)
}
}
}