How it works
The explanation, with examples you can run.
Copies and retains
Copying a struct copies its stored properties. If those include class references, Strings or arrays, each copy is a retain, and each copy going away is a release. The compiler's ownership analysis removes most of them: passing an argument is a borrow by default (the caller keeps ownership, the callee just reads), and the last use of a variable can move it instead of copying. That's why a let value handed to a function usually costs nothing, and why ARC traffic is mostly a problem in hot loops over values full of references.
Exclusive access
Swift enforces a law of exclusivity: while a variable is being modified (an inout argument, a mutating method, a write through a setter), nothing else may read or write it.
func demo() { var stepSize = 1 func increment(_ number: inout Int) { number += stepSize } // reads stepSize while number is being written var other = 10 increment(&other) // fine: different variables print(other) // 11 increment(&stepSize) // runtime trap: "Simultaneous accesses", overlapping access to stepSize}demo()Overlaps the compiler can see (swap(&a, &a), two inout arguments that are the same variable) are compile errors. Others, through class properties or escaping closures, are checked at runtime and trap. This rule is what lets the compiler treat inout as a fast in-place write instead of a copy in and out.
Noncopyable types
Some values should never be duplicated: a file descriptor, a lock, a one-time token. Since Swift 5.9 a struct or enum can be declared ~Copyable (read: "not Copyable"):
struct FileDescriptor: ~Copyable { let fd: Int32 init(fd: Int32) { self.fd = fd; print("open \(fd)") } consuming func close() { print("close \(fd)"); discard self } // ends the value without running deinit deinit { print("deinit closes \(fd)") }} func inspect(_ f: borrowing FileDescriptor) { print("reading \(f.fd)") } func run() { let file = FileDescriptor(fd: 3) inspect(file) // borrowed: file is still ours let moved = file // ownership moves to moved // inspect(file) // error: 'file' used after consume moved.close() // consumed: moved is gone too}run()- A noncopyable value has exactly one owner. Assignment and
consumingparameters move it; using the old name afterwards is a compile error. - It can have a
deinit, like a class, but without reference counting or heap allocation. borrowingparameters read without taking ownership;consumingones take it. For copyable types they are optional performance hints; for~Copyabletypes they are required.- Since Swift 6.0, generics and protocols can accept noncopyable types (
<T: ~Copyable>, SE-0427), andOptionalcan wrap them.
Where this is heading
The standard library builds on these features: InlineArray (Swift 6.2) is a fixed-size array stored inline, Span (Swift 6.2) gives safe, non-escaping access to contiguous memory without copying, and Swift 6.4 adds array types for noncopyable elements. Most app code never writes ~Copyable, but knowing it exists explains new APIs, and the exclusivity rule applies to everyone.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.