withUnsafeBytes and C Interop
Bridge to C APIs that expect raw bytes.
withUnsafeBytes and C Interop is a free Swift Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Swift Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
From Typed to Raw Bytes
Sometimes you need to see a value not as an Int or a struct, but as a sequence of raw bytes — to hash it, write it to a file, or hand it to a C API. withUnsafeBytes gives you exactly that: a temporary view of a value's storage as UInt8.
var value: UInt32 = 0x01020304
withUnsafeBytes(of: &value) { rawBuffer in
print(rawBuffer.count) // 4 bytes
}The UnsafeRawBufferPointer
The closure receives an UnsafeRawBufferPointer — a buffer of bytes with no element type. You index it to read individual UInt8 values. It is read-only; the mutable form is withUnsafeMutableBytes.
var value: UInt32 = 0x01020304
withUnsafeBytes(of: &value) { bytes in
for b in bytes { print(String(format: "%02x", b)) }
}
// Order depends on endiannessEndianness Matters
The byte order you observe depends on the platform's endianness. On little-endian machines (Apple silicon, Intel) the least significant byte comes first. When serializing for a protocol, convert explicitly with bigEndian or littleEndian.
let host: UInt32 = 0x01020304
let networkOrder = host.bigEndian
var be = networkOrder
withUnsafeBytes(of: &be) { bytes in
// Always 01 02 03 04 regardless of platform
print(Array(bytes))
}Bytes of a Struct
withUnsafeBytes(of:) works on any value, including structs. The buffer length equals MemoryLayout<T>.size, and any padding bytes are included but their contents are unspecified.
struct RGB { var r: UInt8; var g: UInt8; var b: UInt8 }
var color = RGB(r: 255, g: 128, b: 0)
withUnsafeBytes(of: &color) { bytes in
print(bytes[0], bytes[1], bytes[2]) // 255 128 0
}Loading a Value From Bytes
To reinterpret raw bytes as a typed value, use load(fromByteOffset:as:) on a raw buffer. The offset must respect the type's alignment, and the bytes must actually represent a valid value of that type.
let raw: [UInt8] = [0x04, 0x03, 0x02, 0x01]
let n = raw.withUnsafeBytes { ptr in
ptr.load(fromByteOffset: 0, as: UInt32.self)
}
print(String(format: "0x%08x", n))loadUnaligned for Packed Data
Binary formats often pack fields without alignment padding. Calling load at an unaligned offset is undefined behavior; use loadUnaligned(fromByteOffset:as:) instead to read safely from any offset.
let packet: [UInt8] = [0xFF, 0x04, 0x03, 0x02, 0x01]
let field = packet.withUnsafeBytes { ptr in
ptr.loadUnaligned(fromByteOffset: 1, as: UInt32.self)
}
print(field)Bridging Swift Data to C
Many C functions accept a const void * plus a length. Data and arrays expose withUnsafeBytes so you can pass baseAddress and count directly without copying.
import Foundation
let data = Data([0x68, 0x69]) // "hi"
data.withUnsafeBytes { (raw: UnsafeRawBufferPointer) in
if let base = raw.baseAddress {
// c_consume(base, raw.count)
print("ptr", base, "len", raw.count)
}
}Receiving a C Pointer
When a C callback hands you a const void * and a length, wrap it in an UnsafeRawBufferPointer to iterate safely. Swift will import the C pointer as UnsafeRawPointer?.
func handleC(_ ptr: UnsafeRawPointer, _ len: Int) {
let buffer = UnsafeRawBufferPointer(start: ptr, count: len)
let checksum = buffer.reduce(0) { $0 &+ Int($1) }
print("checksum:", checksum)
}bindMemory and Reinterpretation
To treat a raw region as typed elements, bindMemory(to:capacity:) tells the compiler how to interpret it. Misusing this (binding the same memory to two incompatible types) violates Swift's strict aliasing rules and is undefined.
let raw = UnsafeMutableRawPointer.allocate(
byteCount: 8, alignment: 8)
defer { raw.deallocate() }
let typed = raw.bindMemory(to: Int.self, capacity: 1)
typed.pointee = 1234
print(typed.pointee)assumingMemoryBound vs bindMemory
bindMemory changes how memory is interpreted permanently; assumingMemoryBound(to:) asserts it is already bound to that type without changing anything. Use the latter only when you are certain of the existing binding, such as memory you know holds the given type.
func sumInts(_ raw: UnsafeRawPointer, _ count: Int) -> Int {
let p = raw.assumingMemoryBound(to: Int.self)
var total = 0
for i in 0..<count { total += p[i] }
return total
}Safety Checklist
Raw-byte and C interop is where most crashes hide. Remember:
- Never let the closure's pointer escape.
- Respect alignment or use the
Unalignedvariants. - Handle
endiannessexplicitly when serializing. - Do not rebind memory to conflicting types.
- Guard against a
nilbaseAddressfor empty buffers.
// Convert any value to a hex string safely
import Foundation
func hex<T>(_ v: T) -> String {
var value = v
return withUnsafeBytes(of: &value) { bytes in
bytes.map { String(format: "%02x", $0) }.joined()
}
}Quick Check
Test your grasp of reading packed binary data.
Recap
You learned to bridge Swift values and C through raw bytes:
withUnsafeBytes(of:)views any value asUInt8.load/loadUnalignedreinterpret bytes as typed values, respecting alignment.Dataand arrays exposewithUnsafeBytesto pass pointer + length to C.bindMemoryandassumingMemoryBoundcontrol how raw memory is typed.- Endianness, alignment, and pointer escape are the main hazards.
Frequently asked questions
Is the “withUnsafeBytes and C Interop” lesson free?
Yes — the full text of “withUnsafeBytes and C Interop” is free to read here on the web, and the Swift Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Swift Academy course, upgrade to CoddyKit PRO.
What will I learn in “withUnsafeBytes and C Interop”?
Bridge to C APIs that expect raw bytes. You practise Swift Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Swift Academy?
No prior experience is required. Swift Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “withUnsafeBytes and C Interop” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Swift Academy lesson?
Yes. Every Swift Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- MemoryLayout and Alignment
- UnsafePointer and UnsafeMutablePointer
- Unsafe Buffer Pointers
- withUnsafeBytes and C Interop