How to declare a fixed-size array in Swift
Table of Contents
Swift 6.2 introduces InlineArray, an array whose length is part of its type.
let fiveIntegers: InlineArray<5, Int> = .init(repeating: 99)
That's a lot of typing next to the dynamic array we've always had.
let fiveIntegers: [Int] = .init(repeating: 99, count: 5)
SE-0483 closes the gap with a shorthand.
let fiveIntegers: [5 of Int] = .init(repeating: 99)
What InlineArray is
InlineArray comes from SE-0453, and its length lives in the type rather than in the value. InlineArray<5, Int> and InlineArray<6, Int> are different types.
Because the size is known at compile time, the elements are stored inline — in the enclosing struct, or on the stack — instead of behind a pointer to heap storage. Declaring one never introduces a heap allocation just to hold its elements.
This is the equivalent of a C array, C++'s std::array, or Rust's [T; N]. Swift went a long time without one.
You can easily support sarunw.com by checking out this sponsor.
Offline Transcription: Fast, privacy-focus way to transcribe audio, video, and podcast files. No data leaves your Mac.
Why the sugar matters
You might read the shorthand as a nicety. The proposal makes a more pointed argument.
Every language in Swift's neighbourhood gives its fixed-size array a short syntax. Swift not only lacked one, it gave the short syntax to Array — the dynamic, heap-allocating type. Sugar signals defaults, so [Int] being the only spellable-in-brackets array implies Array is the right answer everywhere. It often isn't.
Giving InlineArray a bracket form puts the two on more equal footing.
The syntax
The size and the element type are separated by of.
let fiveIntegers: [5 of Int] = .init(repeating: 99)
of reads as a phrase — "an array of five ints" — and follows Swift's habit of short contextual keywords like in.
It nests the way you'd expect.
// Before
let fiveByFive: InlineArray<5, InlineArray<5, Int>> = .init(repeating: .init(repeating: 99))
// After
let fiveByFive: [5 of [5 of Int]] = .init(repeating: .init(repeating: 99))
Either position can be inferred with _.
let fiveIntegers: [5 of _] = .init(repeating: 99)
let fourBytes: [_ of Int8] = [1, 2, 3, 4]
let fourIntegers: [_ of _] = [1, 2, 3, 4]
And it's a type, so it works anywhere a type goes — including on the right-hand side.
let fiveDoubles = [5 of _](repeating: 1.23)
[5 of Int](repeating: 99)
MemoryLayout<[5 of Int]>.size
unsafeBitCast((1, 2, 3), to: [3 of Int].self)
Whitespace is required
of needs a space on both sides.
let a: [5 of Int] // ok
let b: [5 of Int] // ok, no need to balance
let c: [5of Int] // error
A line break may come after of but not before. That asymmetry isn't about ambiguity — it gives the parser better error recovery, and so better diagnostics.
It's only sugar
This is resolved entirely at compile time. It produces the same InlineArray type, appears nowhere in the ABI, and depends on no particular runtime version. Nothing stops you from mixing the two spellings in one codebase.
Two things that will surprise you
InlineArray is not a Collection. The standard library deliberately held off on that conformance, so the usual collection machinery isn't available. It does have indices, so looping still works.
for i in numbers.indices {
print(numbers[i])
}
Array literals do work, but not through ExpressibleByArrayLiteral.
let numbers: InlineArray<3, Int> = [1, 2, 3] // fine
This is a compiler special case that initializes each element in place, precisely so a stack-allocated array doesn't need a heap allocation to get started. The type genuinely doesn't conform to the protocol, which shows up the moment a generic context asks for it.
func test<T: ExpressibleByArrayLiteral>(_: T) {}
test([1, 2, 3] as InlineArray<3, Int>)
// error: 'InlineArray<3, Int>' does not conform to 'ExpressibleByArrayLiteral'
What isn't here yet
The obvious companion to [5 of Int] is a value form.
let fiveInts = [5 of 99]
That is listed as a future direction, not something we can write today. It needs a new expressible-by-literal protocol and a way to map the literal onto an initializer, which is a much larger design. The proposal notes it deliberately chose a type syntax that leaves room for it later.
So for now, repeated values still go through the initializer.
let fiveInts: [5 of Int] = .init(repeating: 99)
You can easily support sarunw.com by checking out this sponsor.
Offline Transcription: Fast, privacy-focus way to transcribe audio, video, and podcast files. No data leaves your Mac.
Summary
[5 of Int] is shorthand for InlineArray<5, Int>, a fixed-size array stored inline rather than on the heap.
The sugar nests, accepts _ in either position, works anywhere a type is written, and is resolved at compile time with no ABI or runtime impact. It needs whitespace around of.
Just remember that InlineArray isn't a Collection and doesn't conform to ExpressibleByArrayLiteral, even though literal initialization looks like it should — and that the value form [5 of 99] is a future direction rather than something available now.
Read more article about Swift, Swift Evolution, Array, or see all available topic
Enjoy the read?
If you enjoy this article, you can subscribe to the weekly newsletter.
Every Friday, you'll get a quick recap of all articles and tips posted on this site. No strings attached. Unsubscribe anytime.
Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.
If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.
Become a patron Buy me a coffee Tweet ShareHow to set a default value in Swift string interpolation
Swift 6.2 adds a default: label to string interpolation, so we can provide a fallback string for any optional value without fighting the type checker.
How to add cases to a public enum without breaking your API
Adding a case to a public enum is a source-breaking change for anyone switching over it. Swift's @nonexhaustive attribute finally lets packages opt out of that.