Home

Values and references

Copies, copy-on-write, and the cost of sharing

Swift’s central design decision is that most things are values. Int, String, Array, Dictionary, every struct and every enum — assigning one gives the destination its own copy. Classes are the exception: assigning one gives you a second name for the same object.

The consequence people quote is “structs are copied, classes are shared”. The consequence that matters is about reasoning: with a value, no other part of the program can change what you are holding. Nobody has a reference to reach through.

struct Point { var x: Int, y: Int }

var a = Point(x: 0, y: 0)
var b = a
b.x = 10
print(a.x)  // 0 — `a` cannot be reached through `b`

Replace struct with class and a.x prints 10. Every function that received a earlier is now holding something different than it was handed, and nothing in the type system marked that possibility.

The copy is not what you think

“Copying an array copies its elements” would make Swift unusable — passing a million-element array to a function would be a million-element memcpy. It does not work that way. Array, String, Dictionary and Set are value types wrapping a reference. The struct is a thin header holding a pointer to a heap buffer. Copying the struct copies the pointer and bumps a reference count.

The buffer is only duplicated when someone writes to a copy — copy-on-write.

var first = [1, 2, 3]
var second = first          // no allocation; both point at one buffer
second.append(4)            // now the buffer is duplicated, because it was shared

You can implement the same trick, and for any type holding a large payload you should:

struct Pixels {
    private final class Buffer {
        var data: [UInt8]
        init(data: [UInt8]) { self.data = data }
    }

    private var buffer: Buffer

    init(data: [UInt8]) { buffer = Buffer(data: data) }

    var data: [UInt8] { buffer.data }

    mutating func setPixel(at index: Int, to value: UInt8) {
        if !isKnownUniquelyReferenced(&buffer) {
            buffer = Buffer(data: buffer.data)
        }
        buffer.data[index] = value
    }
}

isKnownUniquelyReferenced is the whole mechanism. If this value holds the only reference to the buffer, mutate it in place. If anyone else is looking, duplicate first. Note the buffer class is final and private: uniqueness is only meaningful if no subclass and no outside code can hold a reference you did not account for.

Warning

isKnownUniquelyReferenced returns false for Objective-C objects, always. A copy-on-write wrapper around an imported NSObject subclass will copy on every single write.

mutating is a rewrite, not a mutation

A mutating method on a struct does not modify the value in place the way a class method does. It takes the value as inout, and inout means “copy in, copy back out” — semantically. The optimiser usually turns that into an in-place update, but the semantics are what the language guarantees, and they explain otherwise-baffling rules:

struct Counter {
    var count = 0
    mutating func increment() { count += 1 }
}

let fixed = Counter()
// fixed.increment()   // error: `fixed` is a `let`

The call is rejected not because the method touches memory, but because calling it means assigning a new Counter back to fixed.

This is also why mutating methods are unavailable through a protocol existential held in a let, and why a mutating method cannot escape self into a closure.

Choosing between them

Reach for a struct by default. Reach for a class when you have one of these:

  • Identity matters. Two User values with identical fields are the same user; two URLSession objects are not interchangeable no matter how alike they look.
  • The thing is genuinely shared mutable state — a cache, a connection pool, something several parts of the program deliberately observe.
  • You need inheritance, or Objective-C requires a class.

The mistake I see most often is the reverse of what the style guides warn about. It is not “overusing classes”. It is a struct holding a class reference and quietly acquiring reference semantics through the back door:

final class Session { var token: String? }

struct Client {          // looks like a value
    let session: Session // is not one
}

Copy a Client and you have two clients sharing a token. The type says value; the behaviour says reference. If that is what you want, make it a class so readers can see it.