0Pricing
Swift Academy · Lesson

Writable and Reference KeyPaths

Read and write through key paths.

Writable and Reference KeyPaths is a free Swift Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Swift Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Read-Only vs Writable Key Paths

A plain KeyPath can only read a value. To write through a key path you need a writable variant. Swift has two of them, one for value types and one for reference types.

This lesson covers WritableKeyPath for structs and ReferenceWritableKeyPath for classes, and how the [keyPath:] subscript becomes settable.

struct Person {
    var name: String
    var age: Int
}

let writable: WritableKeyPath<Person, Int> = \Person.age
print(type(of: writable))

WritableKeyPath for Value Types

A WritableKeyPath targets a mutable stored property of a value type. To set through it, the instance itself must be a var, because mutating a struct property mutates the whole value.

Use the [keyPath:] subscript on the left side of an assignment to write.

struct Person {
    var name: String
    var age: Int
}

var alice = Person(name: "Alice", age: 30)
let agePath = \Person.age
alice[keyPath: agePath] = 31
print(alice.age)

Why the Instance Must Be a var

For value types, writing through a key path mutates a copy in place. If the instance is a let, there is nothing to mutate, so the assignment will not compile.

The rule mirrors normal property mutation: you can only set a struct's stored property when the struct is held in a variable.

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

var p = Point(x: 1, y: 2)
p[keyPath: \Point.x] = 10
p[keyPath: \Point.y] = 20
print(p)

A Generic Setter Helper

Because a writable key path captures which property to change, you can write a generic function that updates any property of any value type. Pass the instance inout so the change is written back.

This is the foundation of small functional-update utilities.

func update<Root, Value>(_ root: inout Root, _ path: WritableKeyPath<Root, Value>, to value: Value) {
    root[keyPath: path] = value
}

struct Person { var name: String; var age: Int }
var bob = Person(name: "Bob", age: 40)
update(&bob, \.age, to: 41)
print(bob.age)

ReferenceWritableKeyPath for Classes

For classes, Swift provides ReferenceWritableKeyPath. Because a class instance is a reference, you can write through it even when the instance is held in a let constant; the reference does not change, only the object it points to.

This is the key difference from value-type writable key paths.

class Counter {
    var value: Int = 0
}

let counter = Counter()
let path: ReferenceWritableKeyPath<Counter, Int> = \Counter.value
counter[keyPath: path] = 5
print(counter.value)

let Reference, Mutable Object

Notice that the class instance below is a let, yet we can still set its property through the key path. With a struct this would be a compile error.

That is because mutating a class property does not require the reference to be mutable, only the property itself.

class Profile {
    var nickname: String = ""
}

let profile = Profile()
let nickPath = \Profile.nickname
profile[keyPath: nickPath] = "Ace"
print(profile.nickname)

The Type Hierarchy of Key Paths

The writable key path types form a hierarchy. ReferenceWritableKeyPath is a subtype of WritableKeyPath, which is a subtype of KeyPath. So a more specific key path can be used wherever a more general one is expected.

That means you can pass a ReferenceWritableKeyPath to a function that only needs to read via a KeyPath.

class Box { var size: Int = 1 }

func readSize(_ box: Box, _ path: KeyPath<Box, Int>) -> Int {
    box[keyPath: path]
}

let writable: ReferenceWritableKeyPath<Box, Int> = \Box.size
print(readSize(Box(), writable))

Updating Nested Value Types

Writable key paths chain through nested structs. Setting a deeply nested property rewrites each enclosing value up the chain, all in one assignment.

Here we change the city inside a nested address.

struct Address { var city: String }
struct Person { var name: String; var address: Address }

var carol = Person(name: "Carol", address: Address(city: "Oslo"))
carol[keyPath: \Person.address.city] = "Bergen"
print(carol.address.city)

Applying Many Updates

Because writable key paths are values, you can store a list of updates as key path and value pairs and apply them in a loop. This is a tiny patch system.

Here we apply two changes to a settings struct.

struct Settings { var volume: Int; var brightness: Int }

var settings = Settings(volume: 5, brightness: 5)
func apply(_ s: inout Settings, _ path: WritableKeyPath<Settings, Int>, _ v: Int) {
    s[keyPath: path] = v
}
apply(&settings, \.volume, 8)
apply(&settings, \.brightness, 3)
print(settings)

Binding-Style Configuration

A common real-world use is a configure helper that sets a property on a freshly created object and returns it, enabling a fluent style. With classes this works on a let result.

Here we set a label's text through a reference writable key path.

class Label { var text: String = "" }

func configure<T: AnyObject, V>(_ object: T, _ path: ReferenceWritableKeyPath<T, V>, _ value: V) -> T {
    object[keyPath: path] = value
    return object
}

let label = configure(Label(), \.text, "Hello")
print(label.text)

Choosing the Right Key Path Type

Use KeyPath when you only read, WritableKeyPath to set a property of a value type stored in a var, and ReferenceWritableKeyPath to set a property of a class even through a let.

Picking the most specific type that fits keeps your APIs honest about whether they read or mutate.

struct S { var n: Int }
class C { var n: Int = 0 }

let read: KeyPath<S, Int> = \S.n
let writeStruct: WritableKeyPath<S, Int> = \S.n
let writeClass: ReferenceWritableKeyPath<C, Int> = \C.n
print(type(of: read), type(of: writeStruct), type(of: writeClass))

Quick Check: Writable Key Paths

Recall which key path type lets you set a class property even when the instance is a let constant.

Recap: Writable and Reference Key Paths

You learned how to write through key paths:

  • WritableKeyPath sets a stored property of a value type, requiring a var instance.
  • ReferenceWritableKeyPath sets a property of a class and works even through a let.
  • The [keyPath:] subscript appears on the left side of an assignment to write.
  • The types form a hierarchy, so a writable key path can stand in for a read-only one.
  • Generic inout setters and configure helpers build on these.

Next you will see @dynamicMemberLookup, which uses key paths to offer dot-syntax access into dynamic data.

struct Person { var name: String; var age: Int }

var dave = Person(name: "Dave", age: 27)
dave[keyPath: \Person.age] = 28
print("Updated age: \(dave.age)")

Frequently asked questions

Is the “Writable and Reference KeyPaths” lesson free?

Yes — the full text of “Writable and Reference KeyPaths” is free to read here on the web, and the Swift Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Swift Academy course, upgrade to CoddyKit PRO.

What will I learn in “Writable and Reference KeyPaths”?

Read and write through key paths. You practise Swift Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Swift Academy?

No prior experience is required. Swift Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Writable and Reference KeyPaths” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Swift Academy lesson?

Yes. Every Swift Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. KeyPath Basics
  2. Using KeyPaths in Collections
  3. Writable and Reference KeyPaths
  4. @dynamicMemberLookup
← Back to Swift Academy