Noncopyable Types (~Copyable): Ownership Semantics in Swift
Every value in Swift is copyable by default. You assign a struct to a new variable, pass it to a function, return it
from a closure — the compiler silently duplicates it. For most code, that implicit copying is fine. But when you are
modeling a resource that must have exactly one owner — a file handle, a database transaction, a GPU buffer — copies are
not just wasteful, they are semantically wrong. Swift’s ~Copyable types let you suppress implicit copying at the type
level, giving you Rust-style unique ownership without leaving Swift.
This post covers what ~Copyable does, how consuming and borrowing modifiers control ownership transfer, and when
you should (and should not) reach for noncopyable types. We will not cover ~Escapable or Span — those are explored
in Span and MutableSpan.
Contents
- The Problem
- Introducing ~Copyable
- Ownership Modifiers: consuming, borrowing, and inout
- The deinit Story
- Noncopyable Generics
- Advanced Usage
- Performance Considerations
- When to Use (and When Not To)
- Summary
The Problem
Consider a render job in a Pixar-style asset pipeline. Each job holds an exclusive lock on a GPU command buffer. If two parts of your code hold copies of the same job, both might try to commit the buffer, corrupting the frame.
struct RenderJob {
let sceneID: String
let commandBufferToken: UInt64
func commit() {
print("Committing buffer \(commandBufferToken) for scene '\(sceneID)'")
}
}
let job = RenderJob(sceneID: "toy-story-4-final", commandBufferToken: 42)
let backup = job // implicit copy — now two owners
job.commit()
backup.commit() // double commit — data corruption
Committing buffer 42 for scene 'toy-story-4-final'
Committing buffer 42 for scene 'toy-story-4-final'
The compiler is happy, but the runtime behavior is wrong. You could switch to a class and add manual bookkeeping, but
that trades value semantics and stack allocation for heap allocation and reference counting. What you really want is for
the compiler to enforce that RenderJob has exactly one owner at all times.
Introducing ~Copyable
The ~Copyable annotation suppresses the implicit Copyable conformance that every Swift type normally receives. Once
a type is ~Copyable, the compiler rejects any attempt to copy it.
Note:
~Copyablewas introduced in SE-0390 and shipped in Swift 5.9. Noncopyable generics arrived in SE-0427 with Swift 6.0.
struct RenderJob: ~Copyable {
let sceneID: String
let commandBufferToken: UInt64
func commit() {
print("Committing buffer \(commandBufferToken) for scene '\(sceneID)'")
}
}
let job = RenderJob(sceneID: "toy-story-4-final", commandBufferToken: 42)
let backup = job // error: 'job' is borrowed and cannot be consumed
job.commit()
The compiler stops you cold. There is no way to create a second owner of job without explicitly designing your API to
allow it. This is the core value proposition: unique ownership enforced at compile time.
What Happens to the Original Binding
When you pass or assign a noncopyable value, ownership transfers. The original binding is consumed and becomes unavailable.
let job = RenderJob(sceneID: "finding-nemo-rerender", commandBufferToken: 7)
let transferred = consume job
// print(job.sceneID) // error: 'job' used after consume
transferred.commit()
Committing buffer 7 for scene 'finding-nemo-rerender'
The consume keyword makes the ownership transfer explicit. After the consume, job is dead — accessing it is a
compile-time error, not a runtime crash.
Ownership Modifiers: consuming, borrowing, and inout
Functions that accept a ~Copyable parameter must declare how they interact with ownership. Swift provides three
modifiers:
| Modifier | Ownership Effect | Caller Can Use Value After Call? |
|---|---|---|
borrowing | Read-only loan. Caller retains ownership. | Yes |
consuming | Ownership transfers to the callee. Caller loses it. | No |
inout | Exclusive mutable access. Caller keeps ownership. | Yes |
borrowing — Read Without Taking
Use borrowing when a function needs to inspect a value without claiming it.
struct AssetManifest: ~Copyable {
let projectName: String
let assetCount: Int
}
func printSummary(_ manifest: borrowing AssetManifest) {
print("\(manifest.projectName): \(manifest.assetCount) assets")
}
let manifest = AssetManifest(projectName: "Monsters Inc", assetCount: 2048)
printSummary(manifest)
printSummary(manifest) // still valid — borrowing did not consume
Monsters Inc: 2048 assets
Monsters Inc: 2048 assets
The caller retains full ownership. You can call printSummary as many times as you want.
consuming — Transfer Ownership
Use consuming when the callee becomes the final owner of the value.
func submitToRenderFarm(_ job: consuming RenderJob) {
print("Submitting scene '\(job.sceneID)' to render farm")
job.commit()
// job is destroyed at end of scope
}
let job = RenderJob(sceneID: "inside-out-2-shot-47", commandBufferToken: 99)
submitToRenderFarm(job)
// job.commit() // error: 'job' used after being consumed
Submitting scene 'inside-out-2-shot-47' to render farm
Committing buffer 99 for scene 'inside-out-2-shot-47'
After calling submitToRenderFarm, job no longer exists in the caller’s scope. The compiler enforces this statically.
inout — Exclusive Mutable Access
Use inout when you need to mutate the value and return ownership to the caller.
struct LightingPass: ~Copyable {
var intensity: Double
let sceneName: String
mutating func boost(by factor: Double) {
intensity *= factor
}
}
func applyDramaticLighting(_ pass: inout LightingPass) {
pass.boost(by: 2.5)
print("Boosted '\(pass.sceneName)' to intensity \(pass.intensity)")
}
var pass = LightingPass(intensity: 1.0, sceneName: "Coco Marigold Bridge")
applyDramaticLighting(&pass)
print("Final intensity: \(pass.intensity)")
Boosted 'Coco Marigold Bridge' to intensity 2.5
Final intensity: 2.5
The inout modifier grants the callee exclusive, mutable access. Ownership returns to the caller when the function
finishes.
The deinit Story
Noncopyable structs and enums can define a deinit, which is something normally reserved for classes. This is the
natural place to release the exclusive resource.
struct DatabaseTransaction: ~Copyable {
let transactionID: Int
private var committed = false
init(id: Int) {
self.transactionID = id
print("Transaction \(id): BEGIN")
}
mutating func commit() {
committed = true
print("Transaction \(transactionID): COMMIT")
}
deinit {
if !committed {
print("Transaction \(transactionID): ROLLBACK (not committed)")
}
}
}
func saveMovieRating() {
var txn = DatabaseTransaction(id: 1)
// Simulate saving "Ratatouille" with a 5-star rating
txn.commit()
// deinit fires here, but committed == true, so no rollback
}
func failedSave() {
let _ = DatabaseTransaction(id: 2)
// Forgot to commit — deinit triggers automatic rollback
}
Transaction 1: BEGIN
Transaction 1: COMMIT
Transaction 2: BEGIN
Transaction 2: ROLLBACK (not committed)
This pattern — automatic cleanup in deinit — is RAII (Resource Acquisition Is Initialization), a technique borrowed
from C++ and Rust. The difference from using a class is that you get deterministic destruction with value semantics and
no heap allocation.
Tip: When a
consumingfunction receives a noncopyable value, the value’sdeinitruns at the end of that function’s scope — unless you explicitly transfer ownership elsewhere withconsume.
Noncopyable Generics
Before Swift 6.0, generic code implicitly required Copyable conformance. That meant you could not store a ~Copyable
type in a generic container or pass it to a generic function.
SE-0427 changed this by
letting you suppress the Copyable constraint with the same ~Copyable syntax.
struct UniqueBox<Wrapped: ~Copyable>: ~Copyable {
private var _value: Wrapped
init(_ value: consuming Wrapped) {
_value = value
}
consuming func take() -> Wrapped {
return _value
}
borrowing func peek() -> String {
return String(describing: _value)
}
}
The key insight: Wrapped: ~Copyable means “Wrapped does not need to be Copyable.” It still can be Copyable — you are
relaxing the constraint, not adding a new one.
// Works with noncopyable types
let boxedJob = UniqueBox(RenderJob(sceneID: "wall-e-earth", commandBufferToken: 55))
let job = boxedJob.take()
job.commit()
// Also works with regular Copyable types
let boxedNumber = UniqueBox(42)
let number = boxedNumber.take()
print(number)
Committing buffer 55 for scene 'wall-e-earth'
42
Apple Docs:
Copyable— Swift Standard Library
Advanced Usage
Noncopyable Enums for State Machines
Noncopyable enums are excellent for modeling state machines where each state transition consumes the previous state, making illegal transitions unrepresentable.
enum RenderPipelineState: ~Copyable {
case idle
case loading(sceneFile: String)
case rendering(frameCount: Int)
case complete(outputPath: String)
consuming func startLoading(file: String) -> RenderPipelineState {
guard case .idle = self else {
fatalError("Can only start loading from idle state")
}
return .loading(sceneFile: file)
}
consuming func beginRender(frames: Int) -> RenderPipelineState {
guard case .loading = self else {
fatalError("Can only begin render after loading")
}
return .rendering(frameCount: frames)
}
consuming func finish(output: String) -> RenderPipelineState {
guard case .rendering = self else {
fatalError("Can only finish from rendering state")
}
return .complete(outputPath: output)
}
}
Each transition method is consuming — calling it destroys the old state and produces the new one. You cannot hold onto
both .idle and .rendering simultaneously.
var state: RenderPipelineState = .idle
state = state.startLoading(file: "up-house-scene.usd")
state = state.beginRender(frames: 240)
state = state.finish(output: "/renders/up-house-final.exr")
Conditional Copyability
Sometimes a wrapper type should be copyable only when its contents are copyable. Swift supports this with conditional conformance.
struct TaggedValue<T: ~Copyable>: ~Copyable {
let tag: String
var value: T
}
extension TaggedValue: Copyable where T: Copyable {}
When T is Int or String, TaggedValue<T> behaves like any other value type with full copy semantics. When T is
~Copyable, the wrapper respects the inner type’s ownership rules. This is the same strategy the standard library uses
for Optional and Result in Swift 6.0+.
Warning: A
~Copyablestruct that contains a stored property of a copyable type is still noncopyable. The~Copyableannotation applies to the struct itself — it is not inferred from stored properties.
Performance Considerations
The primary performance benefit of ~Copyable is eliminating unintended copies. For small types (a few words of stack
memory), the compiler already optimizes copies away in most cases. The real wins come in two scenarios:
Large value types. If your struct contains a large inline buffer — for example, an InlineArray of pixel data —
each copy duplicates the entire buffer. Marking it ~Copyable guarantees zero copies and forces callers to use
borrowing or inout access.
Deterministic resource cleanup. By using deinit on a noncopyable struct instead of a class, you avoid heap
allocation and reference-counting overhead while still getting deterministic destruction. This matters in tight loops —
render passes, audio processing callbacks, networking hot paths — where ARC traffic can show up as measurable overhead
in Instruments.
Tip: Profile with Instruments before reaching for
~Copyableas a performance optimization. The compiler’s copy-on-write and move optimizations already eliminate most redundant copies for regular types. Use~Copyablewhen you need the semantic guarantee of unique ownership, not just a performance heuristic.
That said, when you combine ~Copyable with InlineArray and stack
allocation, you unlock fixed-size, zero-heap data structures that rival C performance — without sacrificing memory
safety.
When to Use (and When Not To)
Use ~Copyable when:
- Exclusive resource ownership — file handles, GPU buffers, DB transactions. The compiler enforces single ownership.
- State machines with linear transitions — consuming transitions prevent illegal state duplication.
- Large inline value types on hot paths — eliminates accidental copies. Profile first.
Avoid ~Copyable when:
- Small, plain data models (
Movie,Character,Rating) — the cognitive overhead outweighs any benefit. - Types stored in standard
ArrayorDictionary— standard library collections requireCopyableelements (as of Swift 6.0). Use noncopyable generics or custom containers instead. - Shared read access from multiple call sites —
~Copyableenforces exclusive access. Use a class withSendableor an actor.
Note: Swift’s
OptionalandResultgained~Copyable-aware overloads in Swift 6.0. Other standard library types likeArrayandDictionarydo not yet support noncopyable elements. Track the Swift Evolution dashboard for updates.
Summary
~Copyablesuppresses implicit copy semantics, enforcing unique ownership at compile time.- Use
consumingto transfer ownership,borrowingfor read-only loans, andinoutfor exclusive mutation. - Noncopyable structs can define
deinitfor deterministic cleanup — RAII without the heap. - Noncopyable generics (
T: ~Copyable) let you write containers and utilities that work with both copyable and noncopyable types. - Noncopyable enums model state machines where illegal transitions are unrepresentable.
- Profile before optimizing. Reach for
~Copyablewhen you need ownership guarantees, not just performance.
Want to see noncopyable types in action with stack-allocated collections? Head to
InlineArray and Stack-Allocated Collections to learn how InlineArray pairs
with ~Copyable for zero-heap data structures.