Swift Standard Library: InlineArray and Span

August 29, 2025 · View on GitHub

Overview

InlineArray and Span are two new types introduced in Swift 6.2 to enhance performance of critical code. These types are designed to provide more efficient memory usage and better performance in specific scenarios where standard Swift collections might introduce overhead.

Key concepts:

  • InlineArray: A fixed-size array with inline storage that eliminates heap allocation
  • Span: A safe abstraction for accessing contiguous memory without the dangers of unsafe pointers

InlineArray

What is InlineArray?

InlineArray is a specialized array type that stores its elements contiguously inline, rather than allocating an out-of-line region of memory with copy-on-write optimization. This means the elements are stored directly within the array's memory layout, not in a separate heap allocation.

Declaration

@frozen struct InlineArray<let count: Int, Element> where Element: ~Copyable

The count parameter uses Swift's value generics feature to make the size part of the type.

Key Characteristics

  • Fixed size: Size is specified at compile time and cannot be changed
  • Inline storage: Elements are stored directly, not via a reference to a buffer
  • No heap allocation: Can be stored on the stack or directly within other types
  • No dynamic resizing: Cannot append or remove elements
  • No copy-on-write: Copies are made eagerly when assigned to a new variable
  • No reference counting: Eliminates the overhead of retain/release operations
  • No exclusivity checks: Improves performance in hot paths

Initialization

Array literals can be used to initialize an InlineArray:

// Explicit type specification
let a: InlineArray<4, Int> = [1, 2, 4, 8]

// Type inference for count
let b: InlineArray<_, Int> = [1, 2, 4, 8]  // count inferred as 4

// Type inference for element type
let c: InlineArray<4, _> = [1, 2, 4, 8]    // Element type inferred as Int

// Type inference for both
let d: InlineArray = [1, 2, 4, 8]          // InlineArray<4, Int>

Memory Layout

// Empty array
MemoryLayout<InlineArray<0, UInt16>>.size       // 0
MemoryLayout<InlineArray<0, UInt16>>.stride     // 1
MemoryLayout<InlineArray<0, UInt16>>.alignment  // 1

// Non-empty array
MemoryLayout<InlineArray<3, UInt16>>.size       // 6 (2 bytes × 3 elements)
MemoryLayout<InlineArray<3, UInt16>>.stride     // 6
MemoryLayout<InlineArray<3, UInt16>>.alignment  // 2 (same as UInt16)

Basic Usage

// Create an inline array
var array: InlineArray<3, Int> = [1, 2, 3]

// Modify elements
array[0] = 4
// array == [4, 2, 3]

// Cannot append or remove elements
// array.append(4)  // Error: Value of type 'InlineArray<3, Int>' has no member 'append'

// Cannot assign to a different sized inline array
// let bigger: InlineArray<6, Int> = array  // Error: Cannot assign value of type 'InlineArray<3, Int>' to type 'InlineArray<6, Int>'

// Copying behavior
var copy = array    // copy happens on assignment
for i in copy.indices {
    copy[i] += 10
}
// array == [4, 2, 3]
// copy == [14, 12, 13]

Common Properties and Methods

var array: InlineArray<3, Int> = [1, 2, 3]

// Properties
array.count        // 3
array.isEmpty      // false
array.indices      // 0..<3

// Accessing elements
let firstElement = array[0]  // 1

// Iteration
for element in array {
    print(element)
}

// Using indices
for i in array.indices {
    print(array[i])
}

When to Use InlineArray

InlineArray is ideal for:

  • Performance-critical code paths
  • Fixed-size collections that never change size
  • Avoiding heap allocations and reference counting overhead
  • Collections that are modified in place but rarely copied
  • Embedded systems or low-level programming

Not suitable for:

  • Collections that need to grow or shrink
  • Collections that benefit from copy-on-write semantics
  • Collections that are frequently copied or shared between variables

Span

What is Span?

Span is an abstraction that provides fast, direct access to contiguous memory without compromising memory safety. It's designed as a safe alternative to unsafe pointers, providing efficient access to the underlying storage of containers like Array and InlineArray.

Declarations

@frozen struct Span<Element> where Element : ~Copyable

Span represents a contiguous region of memory which contains initialized instances of Element.

var span: Span<Element> { get }
var mutableSpan: MutableSpan<Element> { mutating get }

Instance Properties of Array.

Key Characteristics

  • Memory safety: Ensures memory validity through compile-time checks
  • Lifetime dependency: Cannot outlive the original container
  • No runtime overhead: Safety checks are performed at compile time
  • Direct access: Provides efficient access to contiguous memory
  • Non-escapable: Cannot be returned from functions or captured in closures
  • Prevents common memory issues: Use-after-free, overlapping modification, etc.

Types of Spans

The Span family includes:

  • Span: Read-only access to contiguous elements
  • MutableSpan: Mutable access to contiguous elements
  • RawSpan: Read-only access to raw bytes
  • MutableRawSpan: Mutable access to raw bytes
  • OutputSpan: For initializing a new collection
  • UTF8Span: Specialized for safe and efficient Unicode processing

Accessing Spans

Containers with contiguous storage provide a span property:

let array = [1, 2, 3, 4]
let span = array.span  // Get a Span<Int> over the array's elements

// Data also provides span access
let data = Data([0, 1, 2, 3])
let dataSpan = data.span  // Get a Span<UInt8>

Safe Usage

// Safe usage of a span
func processUsingSpan(_ array: [Int]) -> Int {
    let intSpan = array.span
    var result = 0
    for i in 0..<intSpan.count {
        result += intSpan[i]
    }
    return result
}

Safety Constraints

Spans have compile-time safety constraints:

  1. Cannot escape their scope:
// This won't compile
func getSpan() -> Span<UInt8> {
    let array: [UInt8] = Array(repeating: 0, count: 128)
    return array.span  // Error: Cannot return span that depends on local variable
}
  1. Cannot be captured in closures:
// This won't compile
func getClosure() -> () -> Int {
    let array: [UInt8] = Array(repeating: 0, count: 128)
    let span = array.span
    return { span.count }  // Error: Cannot capture span in closure
}
  1. Cannot access span after modifying the original container:
var array = [1, 2, 3]
let span = array.span
array.append(4)
// let x = span[0]  // Error: Cannot access span after modifying the original container

Example: Implementing a Method Using RawSpan

extension RawSpan {
    mutating func readByte() -> UInt8? {
        guard !isEmpty else { return nil }
        
        let value = unsafeLoadUnaligned(as: UInt8.self)
        self = self._extracting(droppingFirst: 1)
        return value
    }
}

When to Use Span

Span is ideal for:

  • Efficiently operating on contiguous memory without unsafe pointers
  • Processing large amounts of data with minimal overhead
  • Implementing high-performance algorithms that need direct memory access
  • Safely working with the underlying storage of collections
  • Binary parsing and other low-level operations

Performance Considerations

InlineArray vs. Array

Standard Array:

  • Stores elements in a separate heap allocation
  • Uses reference counting to track copies
  • Performs uniqueness checks on mutation
  • Enforces exclusivity at runtime in some cases
  • Supports dynamic resizing

InlineArray:

  • Stores elements directly inline
  • No reference counting overhead
  • No uniqueness checks
  • No runtime exclusivity checks
  • Fixed size known at compile time

Span vs. Unsafe Pointers

Unsafe Pointers:

  • Require manual memory management
  • Prone to memory safety issues
  • No compile-time safety guarantees
  • Can lead to crashes or undefined behavior

Span:

  • Provides memory safety through compile-time checks
  • Prevents use-after-free and similar issues
  • No runtime overhead compared to pointers
  • Cannot outlive the original container

References