# Unsafe Swift: Pointers, Buffers, and Unmanaged

Source: https://devdocs10.com/articles/swift/swift-unsafe-swift-pointers  
By Murat Bo · Updated 2026-10-17 · DevDocs10 (https://devdocs10.com)

Swift usually protects you from memory bugs, but sometimes you need to step outside the safe language features to work with C APIs, binary data, performance-sensitive code, or low-level memory layouts. This article explains Swift’s unsafe pointers, buffer pointers, and `Unmanaged`, with practical examples and the rules you must follow to avoid crashes and undefined behavior.

> **Quick answer:** Use unsafe pointers only when you must interact with memory directly. Prefer the safest pointer type that fits the job, keep pointer lifetimes short, and use `Unmanaged` only when you must manually bridge reference-counted objects to APIs that do not manage ownership for you.

> **Difficulty:** Advanced
>
> **You'll understand this better if you know:** basic Swift variables, value versus reference semantics, and how functions pass arguments.

## 1. What Is Unsafe Swift?

Unsafe Swift refers to a small set of APIs that let you read, write, and pass memory addresses directly. These APIs exist for interoperability and specialized performance work, but they bypass many of Swift’s normal safety guarantees.

- `UnsafePointer` and `UnsafeMutablePointer` point to typed memory.
- `UnsafeRawPointer` and `UnsafeMutableRawPointer` point to untyped bytes.
- `UnsafeBufferPointer` and `UnsafeMutableBufferPointer` describe collections of contiguous elements.
- `Unmanaged` lets you control retain and release behavior for class instances manually.
- These APIs can be correct and necessary, but they require careful lifetime and ownership management.

Unsafe code is not inherently bad. It is simply lower-level and easier to misuse than regular Swift code.

## 2. Why Unsafe Swift Matters

Most Swift programs should avoid unsafe APIs. However, they matter whenever Swift must cooperate with code that speaks in addresses, byte buffers, or manual ownership rules.

Common reasons include:

- Calling C functions that expect pointers.
- Reading or writing binary file formats.
- Working with network packets, cryptography, or image data.
- Reinterpreting memory for interoperability or performance tuning.
- Passing object references through callback APIs that do not retain them.

Used correctly, unsafe APIs let you keep most of Swift’s safety while handling the few places where low-level control is unavoidable.

## 3. Basic Syntax

Unsafe pointers are temporary views into memory. The key idea is that the pointer does not own the memory; it only refers to it.

### Typed pointer example

This example uses a typed pointer to read a value without copying it into a new variable:

```swift
let value = 42

withUnsafePointer(to: value) { pointer in
    print(pointer)
    print(pointer.pointee)
}
```

Output (tested with Swift 6.4):

```text
0x000000016f1de930
42
```

The closure receives a pointer that is valid only inside the closure. `pointer.pointee` reads the value stored at that address.

### Raw pointer example

Raw pointers work with bytes instead of a specific element type:

```swift
let bytes: [UInt8] = [65, 66, 67]

bytes.withUnsafeBytes { buffer in
    print(buffer.count)
}
```

Output (tested with Swift 6.4):

```text
3
```

The closure receives a temporary buffer view over the array’s storage.

## 4. Step-by-Step Examples

### Example 1: Reading a struct as bytes

Sometimes you need the byte representation of a value, for example before hashing or serializing it in a custom format. Use `withUnsafeBytes` to inspect the memory safely within a closure.

```swift
struct Point {
    var x: Int32
    var y: Int32
}

let point = Point(x: 10, y: 20)

withUnsafeBytes(of: point) { rawBuffer in
    for byte in rawBuffer {
        print(byte, terminator: " ")
    }
}
```

Output (tested with Swift 6.4):

```text
10 0 0 0 20 0 0 0
```

This shows how Swift can expose the in-memory bytes of a value, but only while the closure is executing.

### Example 2: Passing an array to a C-style function

Many C APIs expect a pointer plus a count. In Swift, a buffer pointer is the natural match for that pattern.

```swift
func sum(_ buffer: UnsafeBufferPointer<Int>) -> Int {
    var total = 0
    for value in buffer {
        total += value
    }
    return total
}

let numbers = [1, 2, 3, 4]

numbers.withUnsafeBufferPointer { buffer in
    print(sum(buffer))
}
```

Output (tested with Swift 6.4):

```text
10
```

The buffer pointer exposes contiguous storage and a count, which is exactly what low-level APIs often need.

### Example 3: Allocating and initializing memory manually

Sometimes you need storage that is not tied to a Swift collection. In that case, you can allocate memory manually and initialize it yourself.

```swift
let count = 3
let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count)

pointer.initialize(repeating: 0, count: count)

for i in 0..<<count {
    pointer[i] = i * 10
}

for i in 0..<count {
    print(pointer[i])
}

pointer.deinitialize(count: count)
pointer.deallocate()
```

Manual allocation requires a matching deinitialization and deallocation step. If you forget those steps, you can leak memory or corrupt it.

### Example 4: Using Unmanaged for manual ownership

`Unmanaged` is for situations where Swift must hand a class instance through an API that does not automatically retain it.

```swift
final class Token {
    let id = 1
}

let token = Token()
let opaque = Unmanaged.passRetained(token).toOpaque()

// Later, when ownership must be restored:
let restored = Unmanaged<Token>.fromOpaque(opaque).takeRetainedValue()
print(restored.id)
```

Output (tested with Swift 6.4):

```text
1
```

This pattern lets you bridge a reference through an opaque pointer while preserving ownership rules manually.

## 5. Practical Use Cases

- Wrapping a C library that expects pointers and lengths.
- Parsing binary file headers or packet structures.
- Implementing cryptographic or compression routines that work on bytes.
- Interfacing with system frameworks that use raw memory buffers.
- Creating temporary scratch buffers for performance-sensitive code.
- Passing object context through callbacks that store opaque pointers.

These are targeted use cases, not everyday application code. If you can solve the problem with normal arrays, strings, or structs, do that first.

## 6. Common Mistakes

### Mistake 1: Storing a pointer outside its closure

A pointer returned from `withUnsafePointer` or `withUnsafeBytes` is only valid during the closure. Saving it for later produces a dangling pointer.

**Problem:** The pointer outlives the memory access scope, so later reads can crash or return corrupted data.

```swift
var savedPointer: UnsafePointer<Int>?

let value = 99
withUnsafePointer(to: value) { pointer in
    savedPointer = pointer
}

print(savedPointer!.pointee)
```

Output (tested with Swift 6.4):

```text
8203783584
```

**Fix:** Use the pointer only inside the closure, or copy the value out if you need it later.

```swift
let value = 99
var copiedValue = 0

withUnsafePointer(to: value) { pointer in
    copiedValue = pointer.pointee
}

print(copiedValue)
```

Output (tested with Swift 6.4):

```text
99
```

The corrected version works because it copies the data instead of keeping a temporary address alive.

### Mistake 2: Forgetting to deallocate manually allocated memory

Memory you allocate with `allocate(capacity:)` does not clean itself up automatically. You must deinitialize and deallocate it explicitly.

**Problem:** The allocated memory remains reserved, which causes leaks and can become serious in loops or long-running processes.

```swift
let pointer = UnsafeMutablePointer<Int>.allocate(capacity: 2)
pointer[0] = 1
pointer[1] = 2
print(pointer[0], pointer[1])
```

Output (tested with Swift 6.4):

```text
1 2
```

**Fix:** Match each allocation with deinitialization and deallocation when you no longer need the memory.

```swift
let pointer = UnsafeMutablePointer<Int>.allocate(capacity: 2)
pointer.initialize(repeating: 0, count: 2)
pointer[0] = 1
pointer[1] = 2
print(pointer[0], pointer[1])
pointer.deinitialize(count: 2)
pointer.deallocate()
```

Output (tested with Swift 6.4):

```text
1 2
```

The fixed version properly releases the memory and avoids leaks.

### Mistake 3: Using the wrong pointer type for the memory you have

Typed pointers and raw pointers are not interchangeable. A raw byte buffer is not the same thing as a pointer to `Int`, and forcing the wrong type can produce misaligned or invalid reads.

**Problem:** The memory layout does not match the requested type, so Swift cannot safely interpret the bytes as that element type.

```swift
let bytes: [UInt8] = [1, 2, 3, 4]

bytes.withUnsafeBytes { rawBuffer in
    let intPointer = rawBuffer.baseAddress!.assumingMemoryBound(to: Int.self)
    print(intPointer.pointee)
}
```

Output (tested with Swift 6.4):

```text
67305985
```

**Fix:** Bind and load the bytes using the type and alignment that actually match the underlying data.

```swift
bytes.withUnsafeBytes { rawBuffer in
    let firstByte = rawBuffer[0]
    print(firstByte)
}
```

The corrected version reads the data as bytes, which matches the storage and avoids invalid type assumptions.

## 7. Best Practices

### Practice 1: Keep unsafe scopes as small as possible

Use unsafe APIs in the narrowest possible closure or function. Small scopes make pointer lifetimes easier to reason about and reduce the chance of accidental escape.

```swift
var text = "Swift"

text.withUTF8 { buffer in
    print(buffer.count)
}
```

Output (tested with Swift 6.4):

```text
5
```

This is safer than storing a pointer in a property or global variable.

### Practice 2: Prefer buffer pointers for contiguous collections

If you have an array or similar contiguous storage, a buffer pointer communicates both the base address and the number of elements. That is often better than a naked pointer alone.

```swift
func average(_ buffer: UnsafeBufferPointer<Double>) -> Double {
    guard !buffer.isEmpty else { return 0 }
    var sum: Double = 0
    for value in buffer {
        sum += value
    }
    return sum / Double(buffer.count)
}
```

Buffer pointers make length explicit, which reduces mistakes when iterating memory.

### Practice 3: Match ownership methods in Unmanaged carefully

`passRetained` must pair with `takeRetainedValue`, and `passUnretained` must pair with `takeUnretainedValue`. Mixing them causes leaks or over-release crashes.

```swift
final class Session {}

let session = Session()
let raw = Unmanaged.passUnretained(session).toOpaque()
let sameSession = Unmanaged<Session>.fromOpaque(raw).takeUnretainedValue()
print(sameSession)
```

Output (tested with Swift 6.4):

```text
main.Session
```

The ownership API stays correct when the retain and release behavior matches on both sides.

## 8. Limitations and Edge Cases

- Unsafe pointers do not keep memory alive. If the original value deallocates or moves, the pointer becomes invalid.
- Swift arrays and strings can reallocate storage, so never assume a pointer remains valid after mutation.
- Alignment matters. Some data cannot be safely interpreted as another type just because the byte sizes look similar.
- Not all memory is mutable. Trying to write through a read-only pointer can fail or produce undefined behavior.
- `Unmanaged` is only for class references, not structs or enums.
- Raw pointer conversions may compile even when the underlying data does not actually match the requested type, so compile-time success does not guarantee runtime safety.

> A common “it works on my machine” bug in unsafe code is hidden alignment or lifetime trouble. These bugs may appear only on different devices, optimization levels, or operating systems.

## 9. Practical Mini Project

Let’s build a tiny binary-header reader that interprets the first eight bytes of a buffer as two 32-bit integers. This is the sort of task where unsafe byte access is realistic and useful.

```swift
struct Header {
    let magic: UInt32
    let version: UInt32
}

func readHeader(from bytes: [UInt8]) -> Header? {
    guard bytes.count >= 8 else { return nil }

    return bytes.withUnsafeBytes { rawBuffer in
        guard let base = rawBuffer.baseAddress else { return nil }

        let magic = base.load(as: UInt32.self)
        let versionPointer = base.advanced(by: 4)
        let version = versionPointer.load(as: UInt32.self)

        return Header(magic: magic, version: version)
    }
}

let data: [UInt8] = [1, 0, 0, 0, 2, 0, 0, 0]

if let header = readHeader(from: data) {
    print(header.magic, header.version)
}
```

Output (tested with Swift 6.4):

```text
1 2
```

This mini project shows the typical unsafe workflow: validate the data, use a temporary byte view, load typed values carefully, and return a safe Swift value.

## 10. Key Points

- Unsafe Swift APIs let you work directly with memory addresses, bytes, and ownership.
- Use typed pointers for typed memory, raw pointers for bytes, and buffer pointers for contiguous collections.
- Pointer lifetimes are temporary unless you explicitly allocate and manage the memory.
- `Unmanaged` is for manual ownership bridging of class references only.
- Most unsafe bugs come from incorrect lifetime, type, alignment, or ownership assumptions.

## 11. Practice Exercise

Try this small exercise to check your understanding of unsafe buffer access and ownership.

- Write a function that accepts `[UInt8]` and returns the sum of the first four bytes.
- Return `nil` if the array is shorter than four bytes.
- Use a temporary unsafe byte view instead of copying the array.

**Expected output:** For `[10, 20, 30, 40, 50]`, the function should return `100`.

**Hint:** Validate the count first, then use `withUnsafeBytes` and read the first four values by index.

**Solution:**

```swift
func sumFirstFourBytes(from bytes: [UInt8]) -> UInt8? {
    guard bytes.count >= 4 else { return nil }

    return bytes.withUnsafeBytes { buffer in
        return buffer[0] + buffer[1] + buffer[2] + buffer[3]
    }
}

let result = sumFirstFourBytes(from: [10, 20, 30, 40, 50])
print(result as Any)
```

Output (tested with Swift 6.4):

```text
Optional(100)
```

This solution works because it validates the buffer length and uses the unsafe view only inside the closure.

## 12. Final Summary

Unsafe Swift gives you direct access to memory through pointers, buffers, and manual ownership tools. It is essential for C interoperability, binary parsing, and certain performance-sensitive tasks, but it comes with strict rules about lifetime, alignment, initialization, and ownership.

When you use unsafe APIs, keep the scope short, prefer buffer pointers for contiguous collections, and match each manual allocation or retained reference with its corresponding cleanup step. The safest unsafe code is code that leaves the unsafe part as small as possible and returns to normal Swift values quickly.

If you want to go further, the next best topics are Swift memory safety, ARC and reference counting, and how to bridge Swift collections to C APIs without leaking memory.
