Swift ~Copyable Types: Ownership, Borrowing, and Consuming in Swift 6
Learn Swift's ~Copyable types with borrowing, consuming, and the consume operator. Real-world resource wrappers, generics, common pitfalls, and Swift 6 examples.
A Swift non-copyable type (declared with ~Copyable) is a struct, enum, or class-like value that the compiler refuses to duplicate implicitly. Instead of the usual copy-on-assignment behavior every Swift value type inherits, a ~Copyable value has exactly one owner at a time, and you move it around explicitly with the consume operator or by passing it as a consuming parameter. That single change unlocks reliable deinit, safe wrappers around C resources, and zero-cost move semantics for performance-critical code.
~Copyable suppresses the implicit Copyable conformance every Swift type has by default, giving you unique-ownership value types.
Use borrowing when a function only reads the value; use consuming when it takes ownership and may destroy or forward it.
The consume operator ends a binding's lifetime early, and the compiler will then forbid further use of that name.
deinit on a non-copyable struct or enum is finally deterministic, which makes safe wrappers for file descriptors, locks, and Metal buffers straightforward.
Generics accept non-copyable types when you write <T: ~Copyable>. Plain <T> still requires Copyable.
Non-copyable types shine when copying is either wrong (unique resources) or expensive (large buffers, atomic state).
What is a non-copyable type in Swift?
Every ordinary Swift type (Int, String, your app's User struct) silently conforms to a hidden protocol called Copyable. That's why let b = a just works, and why passing a struct into a function creates a fresh copy. Non-copyable types opt out of that contract by writing ~Copyable in the declaration. The tilde is a "suppression" marker: you're telling the compiler "do not add Copyable to this type."
The practical consequence is that assignment stops meaning "duplicate" and starts meaning "move." Once you write let b = a, the compiler treats a as consumed, and any later use of a is a compile error. In older Swift I used to fake this behavior with runtime flags, private initializers, and stern comments. The language now enforces it statically, which is a much nicer place to be.
So, this is not a niche concurrency feature. Non-copyable types are a general tool for expressing single ownership. Rust developers will recognize the shape of it immediately, and so will anyone who wrote Objective-C in the reference-counting days and had to think carefully about who owned each raw pointer. The SE-0390 proposal introduced the feature, and SE-0427 extended it into generics, which is where it really becomes usable in library code.
Declaring ~Copyable structs and enums
Declaring a non-copyable type is one keyword. You add ~Copyable to the inheritance list of a struct or enum. The type may not conform to any protocol that itself requires Copyable, which today is almost every protocol in the standard library, so you have to be deliberate.
Two details are worth pointing out. First, deinit is legal on a struct, which is normally a class-only feature. It runs deterministically when the last binding goes out of scope, which is exactly what you want for a file descriptor. Second, the type has no Copyable-requiring conformances, so there's no Equatable, no Hashable, and it can't go into an ordinary Array<T>. That constraint is precisely what makes it safe: you cannot accidentally double-close() the descriptor, because there is never a second copy of the value to close.
Enums work the same way, and non-copyable enums have a genuinely nice property: you can express state machines where each state owns its resources, and the compiler statically prevents you from reading a state after it has transitioned away.
Borrowing vs consuming: parameter ownership
Because you can't copy a non-copyable value, passing it into a function requires the compiler to know what the function intends to do with it. Swift 6 exposes three parameter modifiers for this, and picking the right one is the single most important skill for working with ~Copyable. In Objective-C you rarely thought about ownership at the parameter level. Everything was a retained pointer. Here, ownership is part of the function's signature.
Modifier
Function may read
Function may mutate
Caller keeps value
Typical use
borrowing
Yes
No
Yes
Read-only inspection, printing, checksums
consuming
Yes
Yes (locally)
No
Take ownership, transform, forward, or destroy
inout
Yes
Yes
Yes (mutated in place)
In-place update where the caller still owns the value
borrowing is the default for non-copyable parameters in most cases, and it's almost always what you want. The function gets a read-only reference; the caller still owns the value after the call returns. consuming takes ownership: after the call, the caller's binding is invalidated, exactly as if they had passed it through consume. inout is the same in-place mutation you already know from ordinary Swift, and it works on non-copyable types too.
func length(of handle: borrowing FileHandle) -> Int {
// Read only. Caller can still use `handle` after this call.
var stat = stat()
fstat(handle.descriptor, &stat)
return Int(stat.st_size)
}
func finish(_ handle: consuming FileHandle) {
// Takes ownership. `handle` is destroyed at the end of this scope.
// Caller cannot use their binding after passing it here.
print("closing at end of scope")
}
The consume operator and end-of-scope semantics
Every non-copyable binding has a lifetime that ends when its owning scope ends. You can also end it early with the consume operator, and the compiler will then forbid any further reference to that binding by name. This gives you precise control over when deinit runs, which matters if the type wraps a scarce resource.
func compress(path: String) throws {
let input = try FileHandle(path: path)
let bytesRead = input.read(into: myBuffer)
// Release the descriptor immediately after we've read what we need.
consume input
// The compiler rejects this line:
// _ = input.read(into: myBuffer) // error: 'input' used after consume
heavyCompressionWork(over: bytesRead)
}
Without the consume call, the file would stay open until compress(path:) returned. With it, the descriptor closes right after the read, and any accidental re-use is a compile error rather than a runtime bug. Honestly, I find this pattern most valuable in long-lived scopes (an actor method, a request handler, a long Task) where holding resources for the whole scope would be sloppy.
There's also a subtle interaction with _ = expression. Assigning a non-copyable value to _ is legal and immediately consumes it. I use that as a shorthand for "run the deinitializer now and drop this thing." It reads more like ordinary Swift than the consume keyword, and is often clearer at call sites where you don't need to name the binding.
Non-copyable methods: consuming, borrowing, and mutating
Methods on a ~Copyable type declare their ownership just like free functions. The default is borrowing, which corresponds to the ordinary "read-only method" that you'd write on any struct. Add mutating for in-place changes, and consuming for methods that must take ownership of self, usually because they transform the value into something else.
struct Buffer: ~Copyable {
private var storage: UnsafeMutableBufferPointer<UInt8>
borrowing func peek(at index: Int) -> UInt8 {
storage[index]
}
mutating func write(_ byte: UInt8, at index: Int) {
storage[index] = byte
}
consuming func into<T>(_ transform: (borrowing Self) -> T) -> T {
let value = transform(self)
return value
// deinit runs at end of scope, releasing storage.
}
}
The consuming method is the interesting one. It's how you build APIs that "hand off" a resource: the caller ends its lifetime by calling .into(_:), the method inspects the value one last time through a borrowing closure, and then the buffer's deinitializer releases the underlying memory. I lean on this pattern for anything that resembles a "finalize" step. Building the immutable output from a mutable builder is the classic example.
A subtle rule: you cannot call a consuming method through a borrowing binding, because a borrower doesn't have ownership to give away. This trips up newcomers, especially when they try to write a close() method that consumes self and then call it from a function that only borrows the value. The fix is either to reshape the API so the caller owns the value, or to move the closing logic into deinit.
Generics and ~Copyable constraints
Prior to Swift 5.9, generic parameters silently required Copyable, so Array<T>, Optional<T>, and everyone else refused to accept your ~Copyable struct. SE-0427 fixed that by allowing you to explicitly relax the requirement with ~Copyable in the generic constraint list.
// A generic pair that accepts both copyable and non-copyable elements.
struct Pair<First: ~Copyable, Second: ~Copyable>: ~Copyable {
var first: First
var second: Second
}
// A function that takes any borrowable value.
func inspect<T: ~Copyable>(_ value: borrowing T, with body: (borrowing T) -> Void) {
body(value)
}
The mental model is inverted from what you're used to. Normally, a generic parameter is a "narrowest possible" placeholder that widens as you add constraints (T alone means "any type", T: Equatable means "any equatable type"). With ~Copyable, the placeholder is widened: you're removing the implicit constraint that T be copyable, so the resulting generic can accept a strictly larger set of types.
The standard library has been slowly adopting this. Optional is now conditionally ~Copyable, which means FileHandle? is legal, and Swift 6.2 extended the pattern to Result, UnsafePointer arithmetic, and the low-level buffer types. If you're building an in-house type that's non-copyable, expect to write your own container types too. The collections in the standard library are getting there, but they still don't universally accept non-copyable elements. Our guide to Swift InlineArray and Span covers the ones that do.
Real-world resource management with deinit
The killer application for non-copyable types is safe wrappers around resources that the operating system, hardware, or an external library owns. Before ~Copyable, you had two options: use a class (which gave you deinit but paid for a heap allocation and reference count on every value) or use a struct and hope callers remembered to call close(). Neither is satisfying.
Here's a real pattern I use for a Metal command buffer wrapper (I hit this exact shape shipping a compute-heavy renderer last year):
import Metal
struct CommandBufferHandle: ~Copyable {
private let buffer: MTLCommandBuffer
init(from queue: MTLCommandQueue) throws {
guard let cb = queue.makeCommandBuffer() else { throw MetalError.noBuffer }
self.buffer = cb
}
consuming func commit() {
buffer.commit()
// deinit runs immediately after this method returns, releasing the Metal buffer.
}
borrowing func encode(_ pass: (MTLCommandBuffer) -> Void) {
pass(buffer)
}
}
A few properties fall out for free. There's no way for a caller to accidentally commit() twice, because the second call fails at compile time (the first consumed self). There's also no way to leak the buffer: if the caller drops the value without calling commit(), the deinitializer still runs. And there's no reference-counting overhead. This is a plain struct, so the compiler can pass it in registers when the surrounding function is inlined.
The same pattern generalizes to database transactions, GPU resources, cryptographic contexts, and any file/socket/pipe wrapper. If your API has a "must call finish()" or "must not call after close()" rule, moving that rule into the type system with ~Copyable is usually the right move.
When should you use non-copyable types?
My rule of thumb: reach for ~Copyable when copying the value would either be incorrect or expensive enough to matter. Most application-layer types fail both tests, and forcing non-copyable semantics on them just adds noise. Here are the cases where I've found it genuinely earns its keep.
Unique operating-system resources
File descriptors, socket handles, mutexes, GPU command buffers, cryptographic contexts. Anything where "having two copies" is a bug. This is the strongest case, and it's where the language design was clearly aimed.
Performance-critical buffers
Large contiguous buffers where copy-on-assignment would blow past a cache line and pessimize the surrounding hot loop. Combined with Span and InlineArray, you can express zero-copy pipelines that were previously the domain of C. The Swift InlineArray and Span guide covers those adjacent types in depth.
State machines with owned data
Enums where each case owns a distinct payload and the transition between cases must be linear. Non-copyable enums make the "you cannot inspect the old state after transitioning" rule a compile error rather than a runtime discipline.
Values crossing concurrency boundaries
Non-copyable types compose well with the sending semantics introduced in Swift 6.2's approachable concurrency. A value that has exactly one owner is trivially safe to hand across an actor boundary, since there's no shared state to reason about because there's no shared state at all.
Common pitfalls and compiler errors
The compiler diagnostics around ~Copyable have improved considerably in Swift 6.1 and 6.2, but there are still a handful of errors that show up repeatedly. Learning to recognize them saves a lot of time.
"Missing reinitialization" and "used after consume"
You'll see this whenever you accidentally reference a binding after it was consumed. The fix is either to reorder the operations so the read happens first, or to hold onto the value with a consuming func that returns it. Type-safe error handling helps here. See the typed throws guide for how to propagate ownership through throws without breaking the compiler.
Optional chaining on non-copyable optionals
Because Optional<T> is conditionally ~Copyable, you cannot use ordinary optional chaining on a non-copyable optional. The chained access would need to borrow the wrapped value, but the borrow rules are stricter here. Unwrap with if let using a fresh binding, and be aware that the binding itself is subject to the same move rules.
Protocol conformances that silently require Copyable
Almost every protocol in the standard library assumes Copyable. If you conform your ~Copyable type to, say, CustomStringConvertible, you'll get an error explaining that Self is required to be copyable. The workaround is usually to expose a borrowing "description" method by hand instead. Apple's official Swift language reference lists which stdlib protocols have been updated with ~Copyable-tolerant requirements.
Captures in escaping closures
An escaping closure can outlive the scope of its captured bindings, so the compiler forbids capturing a non-copyable value by name in an escaping closure. Non-escaping closures are fine, and are how the borrowing / consuming method patterns above work. When you need to hand off ownership into a background task, either mark the closure non-escaping or wrap the value in a class that manages its lifetime. See our Swift actors guide for the concurrency side of that pattern.
Frequently Asked Questions
What is the difference between borrowing and consuming in Swift?
A borrowing parameter grants read-only access and leaves ownership with the caller, so the caller can keep using the value after the call. A consuming parameter takes ownership from the caller. After the call returns, the caller's binding is invalidated and any further use is a compile error. Use borrowing for inspection, consuming for handoff or destruction.
Why does Swift have non-copyable types?
They exist to express unique-ownership semantics at the type level. That gives you deterministic deinit on value types (essential for wrapping file descriptors, GPU resources, and locks), zero-cost move semantics for large buffers, and compile-time enforcement of "use exactly once" rules that were previously runtime concerns.
Can I put a ~Copyable type into an Array?
Not into the standard Array<T> today. Its element type is still constrained to Copyable. Swift 6.2 introduces non-copyable-aware containers, and InlineArray already accepts non-copyable elements. For most cases you can hold a fixed-capacity storage manually or wrap the values in a class-backed container.
Do non-copyable types replace classes?
No. Classes still make sense when you need reference semantics, where many owners point at the same mutable state. Non-copyable types are for the opposite case: exactly one owner, value semantics, deterministic cleanup. Reach for a class when identity matters; reach for ~Copyable when uniqueness matters.
Does ~Copyable affect performance?
Yes, and usually favorably. Because the compiler knows there's only ever one owner, it can move the value's storage instead of copying it, and it can inline the deinitializer where the last use occurs. For large buffers this can be a significant win; for small values (a couple of words) the difference is usually noise.
SwiftUI TextRenderer in iOS 26 gives you glyph-, run-, and line-level control over Text drawing. Learn the protocol, three working effects, and the Dynamic Type edges that trip most people up.
A practical guide to Apple's unified logging in Swift for iOS 26: how Logger replaces print, when to use each log level, how privacy annotations protect PII, and how OSLogStore and OSSignposter power in-app diagnostics and performance tracing.