Start with a struct for a model representing a value; choose a class when identity or shared object state is required. Wrapping an object in a struct does not make the object independent or automatically thread-safe.
Mechanism and design choice
Assigning a struct copies its stored values. If a property holds a class reference, the reference is copied while the referenced object remains shared. Separate this from deep copying and thread safety. Inheritance and Objective-C object APIs can also influence the choice. Primary source
Reproduce the experiment
The experiment copies an integer in Point, a Box reference, and an Envelope holding a Box. It separates changing the object’s property from replacing the copied envelope’s box with a new object. Preconditions check both original and copied values at each step.
This standalone example was run on 2026-10-23 with Apple Swift 6.4 targeting arm64 macOS, in Swift 6 language mode. It needs no network or additional packages.
Download the complete Swift file
Run the command in the download directory. Preconditions stop execution if a check fails. Do not use -Ounchecked: it removes these checks.
swift -swift-version 6 swift-class-vs-struct.swift
struct Point { var x: Int }
final class Box {
var x: Int
init(_ x: Int) { self.x = x }
}
struct Envelope { var box: Box }
let point = Point(x: 0)
var copiedPoint = point
copiedPoint.x = 10
precondition(point.x == 0 && copiedPoint.x == 10)
let box = Box(0)
let copiedReference = box
copiedReference.x = 10
precondition(box === copiedReference && box.x == 10)
let envelope = Envelope(box: Box(0))
var copiedEnvelope = envelope
copiedEnvelope.box.x = 20
precondition(envelope.box.x == 20)
copiedEnvelope.box = Box(30)
precondition(envelope.box.x == 20 && copiedEnvelope.box.x == 30)
print("struct=0,10 class=10,10 nested-reference=20,20 replacement=20,30")
Recorded output and interpretation
struct=0,10 class=10,10 nested-reference=20,20 replacement=20,30
Point diverges into 0 and 10; the Box references share 10. Mutating the object through the copied Envelope makes both observe 20. Replacing that envelope’s box with Box(30) leaves the original at 20. Property independence and shared object identity are distinct.
Scope and limitations
This sequential experiment checks copy semantics, not races or performance. Sharing a mutable variable or a nested reference still requires isolation or synchronization. Swift 6 Sendable checking is not equivalent to merely declaring a struct.
What to check next
List which state should change together after copying a model. For unintended reference sharing, prefer immutable values or define an explicit cloning policy. For an efficiently copied large backing store, continue with the CoW experiment.
Sources and verification
- The Swift Programming Language: Structures and ClassesSwift.org · Official documentation · Checked October 23, 2026Supports: Source for the implementation contract and explanation. The output was checked separately using the experiment included here.

