When are unsafe pointers justified?
What you’re giving up
- No bounds checking
- No ARC
- No type safety (especially
UnsafeRawPointer) - No optionality/nil-safety guarantees
Also invisible to Sendable checking — a hole in concurrency safety.
When it’s actually justified
- C/Objective-C interop where the API demands raw pointers.
- Performance-critical, hot-path code where bounds checking is a measured, real bottleneck —
UnsafeBufferPointerused withwithUnsafeBufferPointer. - Implementing your own low-level data structure or memory pool.
- Reinterpreting memory across type boundaries — e.g.
withUnsafeBytes(of:). - FFI with other languages generally.
When it’s NOT justified
- “It might be faster” without profiling.
- Avoiding
Optionalunwrapping ceremony. - Bridging between Swift types that already have safe conversion paths.
- “I don’t want to deal with
Sendableerrors.”
The discipline once you’ve decided it’s justified
- Scope the unsafe region as tightly as possible.
- Never let an unsafe pointer outlive the closure/scope that produced it.
- Document the invariant you’re relying on.
- Prefer the most specific unsafe type available.
One-liner if pressed: “Unsafe pointers are justified at genuine boundaries where Swift’s safety model doesn’t apply anyway — C/Objective-C interop, raw byte-level reinterpretation, or hand-rolled low-level data structures — and, more rarely, in hot paths where profiling shows bounds-checking or ARC overhead is a real bottleneck; anywhere else, reaching for them trades away compiler-enforced safety for a convenience or perceived performance win that usually isn’t real.”