Swift & Objective-C

[Advanced Swift #8] ~Copyable, Ownership, and Noncopyable Types

Resources such as file handles and locks are meaningful only when unique, so they must not be copied. This guide explains how Swift 5.9’s ~Copyable encodes noncopyability in a type, covers borrowing, consuming, and inout, and clarifies where to use them.

6 min read
Cover image for [Advanced Swift #8] ~Copyable, Ownership, and Noncopyable Types

In the value-type chapter, we summarized Swift’s default behavior as “copying”: assignment copies, and passing to a function copies.

This continues from the previous article Advanced Swift #7.

But what if some values must not be copied? Consider resources that are meaningful only when there is “exactly one in the world,” such as file handles, mutex locks, and bank-transfer tokens.

This installment of the advanced series focuses on ownership. Swift 5.9’s ~Copyable (noncopyable types), borrowing, and consuming open that door. SE-0390: Noncopyable Structs and Enums

If you know Rust, this may feel familiar—and yes, it is. Swift has adopted Rust’s core ideas in its own way.

The direction differs, though. Rust makes ownership the default with no exceptions, while Swift makes copying the default and treats ownership as an optional tool.

The Gap in a Copyable-by-Default World

Every Swift type is Copyable by default. Even without declaring it, the compiler implicitly makes the type conform to the protocol.

That is where the copy semantics of assignment and passing discussed in the value-type chapter come from. It is an excellent default for most values, such as numbers, strings, and coordinates.

The problem is resource-representing types. Consider a struct wrapping a file descriptor.

struct FileHandle {
    let fd: Int32
    func close() { /* fd Close */ }
}

let a = FileHandle(fd: open("data.txt"))
let b = a          // Copy — now two people know the same fd
a.close()
b.close()          // Close an already closed fd again — undefined behavior

The moment a handle is copied, “who is responsible for closing it?” becomes ambiguous. Unauthorized copying is the root of resource bugs such as double release and using a closed handle.

The conventional solution has been to use a class and close it in deinit, centralizing management around a single reference.

It works, but it incurs the costs discussed in the ARC (Automatic Reference Counting) chapter: the heap and reference counting.

Moreover, the compiler still does not know that “this must not be copied” is a rule.

~Copyable — Encoding Noncopyability in a Type

Swift 5.9’s answer is noncopyable types (Swift Evolution proposal SE-0390). The ~Copyable prefixed with a tilde declares that the type “does not conform to Copyable.” SE-0390: Noncopyable Structs and Enums

struct FileHandle: ~Copyable {
    let fd: Int32

    consuming func close() { /* fd Close */ }
    deinit { /* Close here if it has not already been closed */ }
}

let a = FileHandle(fd: open("data.txt"))
let b = a          // Not a copy but a move — ownership transfers to b
// print(a.fd)     // Compile error: a has already been consumed

The behavior changes fundamentally. Assignment becomes a move rather than a copy, and a variable whose ownership was transferred cannot be used afterward.

A violation produces a compile error, not a runtime crash. The compiler keeps a ledger enforcing the rule that “there is exactly one owner of this value at all times.”

Just as optionality turned nil and Sendable turned races into type issues, resource uniqueness becomes a type issue.

There is also a bonus: a struct can declare deinit, so deterministic cleanup runs when its owner leaves scope.

This enables RAII (Resource Acquisition Is Initialization) style resource management without classes.

Diagram contrasting double-release bugs caused by copying with a design that prevents them through moves
The bug class called double release becomes a compile error

borrowing and consuming — Three Ways to Pass Values to Functions

Passing a noncopyable value to a function raises a new question. Since you cannot copy it, must you lend it or transfer it?

Parameter modifiers express that choice.

borrowing means lending. The function only reads the value, while ownership remains with the caller.

The caller can continue using the value after the function returns. func checksum(of handle: borrowing FileHandle) -> Int It is for read-only work such as this.

consuming means transferring. Ownership moves to the function, and the caller can no longer use the value. The consuming func close() above has exactly this meaning.

When using a handle after calling close becomes a compile error, the bug class of “using a closed handle again” disappears at the syntax level.

It also models domain concepts that should disappear when used, such as bank-transfer tokens and one-time tickets.

inout means the familiar pattern: lend it and modify it. Together, the three complete the ownership vocabulary of function parameters: read-only (borrowing), take (consuming), and modify (inout).

These modifiers can also be applied to Copyable types. In that case, they serve as performance hints rather than semantic requirements.

They replace retain/release or copying from the default convention with borrowing and moves to reduce ARC traffic. Requiring an explicit move with the consume operator (let b = consume a) belongs to the same family.

This is a micro-optimization area where measurement must come first. The warning about dispatch convenience applies here as well.

Practical Guidance — Where to Use It and Where Not To

It is important to understand exactly where this feature currently fits.

It fits unique ownership of resources. Examples include file and socket-handle wrappers, lock tokens, transaction guards, and hardware access rights.

Embedded Swift has been one of the main drivers of this feature. Microcontrollers cannot easily afford the heap or ARC, so they need a way to manage resources safely without classes.

The values returned by the standard library’s Mutex and newer types such as Span follow this lineage.

Copyable structs remain the default for ordinary app code. The value-oriented principle is unchanged.

Applying ~Copyable preemptively to domain data is overengineering. It also still creates friction with the generic ecosystem: noncopyable types do not immediately compose with existing generics and collections that assume Copyable, so the language is relaxing these constraints gradually.

For now, reserve it as a specialized tool for types where the answer to “Would copying this be a bug?” is yes.

To close with a comparison to Rust, Rust is an “ownership-by-default” language that enforces ownership rules for every value and even requires lifetime annotations.

Swift keeps its copy-by-default world and moves only the types that need it into the ownership world: “opt-in ownership.”

This is a textbook example of Progressive Disclosure. Code written by developers who do not need ownership contains none of these concepts; the next step opens only for those who need it.

Diagram comparing borrowing, consuming, and inout to three service windows
Read only (borrowing), take (consuming), modify (inout)

Summary

  • All types are implicitly Copyable, and unauthorized copying of resource types is the root of double-release bugs and related issues.
  • ~Copyable (SE-0390) forbids copying, turns assignment into a move, and makes using a consumed variable a compile error. It also permits deinit in structs for deterministic cleanup.
  • Parameter ownership vocabulary: borrowing (read-only, lend), consuming (take, transfer), and inout (modify). consuming methods encode “disappears when used” semantics in the syntax.
  • Use it for unique ownership of resources—handles, locks, tokens, and embedded systems—while Copyable structs remain the default for ordinary data. Unlike Rust’s universal ownership, Swift uses opt-in ownership.

The next installment is both the final advanced topic and the final topic in the Swift series: the full story behind “the app suddenly got smaller in Swift 5”—ABI (Application Binary Interface) stability. SE-0390: Noncopyable Structs and Enums

Sources and Verification Criteria

  • SE-0390: Noncopyable Structs and Enums — Swift Evolution · original standard and specification · verified 2026-08-17 · basis: Swift 5.9’s ~Copyable, consuming and borrowing, and noncopyable-type rules

Continue reading