# When to Choose a Swift Struct Over a Class: 10 Performance and Safety Benefits

> Discover 10 performance and safety benefits for choosing Swift structs over classes. Learn when to prefer value semantics, zero ARC overhead, and thread-safe data.

- Repository: [Apple/swift](https://github.com/apple/swift)
- Tags: deep-dive
- Published: 2026-02-16

---

**Prefer a Swift `struct` over a `class` when you need value semantics, zero ARC overhead, thread-safe data sharing, and predictable memory layout without sacrificing performance through copy-on-write optimization.**

Swift provides two fundamental data modeling tools: **value types** (`struct`, `enum`) and **reference types** (`class`). While classes enable inheritance and shared mutable state, the Swift standard library and compiler are architected to make structs the default choice for most data containers. According to the `apple/swift` repository documentation and implementation, structs provide specific performance and safety guarantees that classes cannot match.

## Copy-on-Write Value Semantics

Structs in Swift exhibit **value semantics**, meaning each variable holds an independent copy of the data. However, the compiler implements **copy-on-write (COW)** optimization to avoid expensive deep copies until mutation actually occurs.

As documented in [[`docs/OwnershipManifesto.md`](https://github.com/apple/swift/blob/main/docs/OwnershipManifesto.md)](https://github.com/apple/swift/blob/main/docs/OwnershipManifesto.md), COW allows large value types like `Array` and `String` to share underlying storage between copies, performing the actual copy only when one instance modifies the data. This gives you the safety of value semantics with the performance characteristics of reference semantics.

## Zero ARC Overhead and Predictable Memory

Unlike classes, which are allocated on the heap and managed by **Automatic Reference Counting (ARC)**, structs typically live on the stack or inline within other values. This eliminates the runtime cost of retain and release operations.

The [[`docs/SIL/Types.md`](https://github.com/apple/swift/blob/main/docs/SIL/Types.md)](https://github.com/apple/swift/blob/main/docs/SIL/Types.md) documentation notes that value types enable more aggressive compiler optimizations, including monomorphization of generic code and better cache locality due to predictable memory layout. When the compiler knows the exact size and alignment of a struct, it can inline storage directly into parent containers rather than requiring pointer indirection.

## Thread Safety by Default

Because each copy of a struct is independent, value types are inherently **thread-safe** without requiring locks or synchronization primitives. When you pass a struct to another thread, you pass a copy of the data, not a reference to shared mutable state.

This aligns with the **Law of Exclusivity** described in [[`docs/OwnershipManifesto.md`](https://github.com/apple/swift/blob/main/docs/OwnershipManifesto.md)](https://github.com/apple/swift/blob/main/docs/OwnershipManifesto.md), which guarantees that memory accessed for writing cannot be simultaneously accessed for reading or writing by any other code. This static enforcement prevents data races at the language level.

## Identity-Free Semantics and Hashable Conformance

Structs have no **object identity**—two instances are equal if and only if their stored properties are equal. This simplifies equality comparisons and makes structs natural candidates for use as dictionary keys or set elements.

The standard library implements `Set` and `Dictionary` to work optimally with value types. As shown in [[`stdlib/public/core/SetStorage.swift`](https://github.com/apple/swift/blob/main/stdlib/public/core/SetStorage.swift)](https://github.com/apple/swift/blob/main/stdlib/public/core/SetStorage.swift), these collections rely on the stable hash values and value semantics of their elements to provide deterministic behavior and efficient lookups.

## Reduced Boilerplate and Generic Performance

Structs require less ceremony than classes. You do not need to write initializers for stored properties unless you need custom logic, and there is no inheritance hierarchy to manage. As noted in [[`docs/StandardLibraryProgrammersManual.md`](https://github.com/apple/swift/blob/main/docs/StandardLibraryProgrammersManual.md)](https://github.com/apple/swift/blob/main/docs/StandardLibraryProgrammersManual.md), the standard library guidelines encourage keeping struct definitions short and focused.

Additionally, generic code operating on structs can be **monomorphized**—the compiler generates specialized machine code for each concrete type used, eliminating the overhead of dynamic dispatch. This is significantly harder to achieve with reference types that rely on virtual method tables.

## Practical Code Examples

### Value Type with Copy-on-Write

```swift
struct Point {
    var x: Double
    var y: Double
    
    mutating func translate(byX dx: Double, byY dy: Double) {
        x += dx
        y += dy
    }
}

let origin = Point(x: 0, y: 0)
var point = origin
point.translate(byX: 3, byY: 4)

// origin remains (0,0) due to value semantics
print(origin)  // Point(x: 0.0, y: 0.0)
print(point)   // Point(x: 3.0, y: 4.0)

```

### Reference Type for Shared State

```swift
final class Person {
    var name: String
    var friends: [Person] = []
    
    init(name: String) {
        self.name = name
    }
}

let alice = Person(name: "Alice")
let bob = alice  // Both reference the same instance

bob.name = "Alicia"
print(alice.name)  // "Alicia" - shared mutable state

```

## Summary

- **Copy-on-write optimization** gives structs the performance of reference types while maintaining value semantics and safety.
- **Zero ARC overhead** eliminates retain/release cycles and heap allocations, improving runtime performance.
- **Thread safety by default** prevents data races without locks because each copy is independent.
- **Identity-free semantics** simplify equality checks and enable use as dictionary keys and set elements.
- **Better generic performance** through compiler monomorphization and predictable memory layout.
- **Less boilerplate** compared to classes, with automatic memberwise initializers and no inheritance complexity.

## Frequently Asked Questions

### When is a Swift class better than a struct?

Choose a `class` when you need **reference semantics** for shared mutable state, such as managing a shared cache, implementing the observer pattern, or when you require inheritance for polymorphic behavior. Classes are also necessary when you need a deinitializer (`deinit`) to clean up resources, or when working with Objective-C APIs that expect objects.

### Do structs always live on the stack?

No, while small structs typically live on the **stack** or inline within other values, large structs or those captured in closures may be allocated on the **heap**. However, even when heap-allocated, structs avoid the reference counting overhead that classes incur, and the compiler optimizes their layout for cache locality.

### Can structs have methods and conform to protocols?

Yes, structs can have **methods**, **computed properties**, **property observers**, and can conform to **protocols** just like classes. They can also implement `mutating` methods that modify the struct's properties. The key difference is that structs cannot inherit from other structs or classes, and they do not support deinitializers or stored properties that are references to the instance itself.

### How does copy-on-write work with custom structs?

The Swift standard library implements copy-on-write for types like `Array` and `String` using internal reference counting of a backing storage class. For your own custom structs, you can implement COW by wrapping a reference type (a class) as private storage and checking for uniqueness before mutation. However, for small value types, the compiler optimizations often make explicit COW unnecessary unless you are managing large, expensive-to-copy buffers.