Did you know Swift implementation has a _modify/yield property accessor?

var value: String {
    get { _value }
    _modify { 
        yield &_value 
    }
}

It modifies the String in place without creating a copy (example). Note this optimizes operations on value types (structs), but has no benefit for reference types (classes and actors) since they are already passed as reference.

_modify vs set

struct ModifyInPlace {
    private var storage = "hello"
    
    var withModify: String {
        get { storage }
        _modify { yield &storage }
    }
    
    var withGetSet: String {
        get { storage }
        set { storage = newValue }
    }
}

var example = ModifyInPlace()
example.withModify.withUTF8 { ptr in
    print("Original: \(ptr.baseAddress as Any)")
}

// will change in place
example.withModify.append(" world")
example.withModify.withUTF8 { ptr in
    print("After _modify: \(ptr.baseAddress as Any)")
}

// will create a copy
example.withGetSet.append(" world")
example.withGetSet.withUTF8 { ptr in
    print("After set: \(ptr.baseAddress as Any)")
}
Prints
     Original: Optional(0x000000016d692df8)
After _modify: Optional(0x000000016d692df8) <- same object
    After set: Optional(0x0000600000e601e0) <- copy

_modify/yield

_modify is a Swift property accessor that provides exclusive, mutable access to a property’s storage location for in-place modifications. Exclusive means that for the full duration of the coroutine, the modify body has exclusive access to the variable.

yield transfers an inout reference to the property’s value, allowing direct modification without creating intermediate copies. Without _modify, a compound operation would create a copy, modify it, and set the new value. With _modify, these operations modify the value directly in its storage location.

Coroutine control flow

A coroutine is a function that is able to suspend and resume computation. _modify/yield is an example of that because yield transfers control to the caller and then resumes executing the _modify block (example).

Coroutine behavior

struct Counter {
    private var value: Int = 0
    
    var current: Int { 
        get { value }
        _modify {
            print(value)  // starts here
            yield &value  // transfers control
            print(value)  // and resumes execution
        }
    }
}

var counter = Counter()
counter.current += 1

The compiler checks that there is exactly one yield on every execution path inside _modify. If the code we yielded to throws, the _modify body terminates immediately (after running any defer there may be) and control is returned to the caller (example).

Terminates on throw

import Foundation

struct Counter {
    private var value: Int = 0
    
    var current: Int {
        get { value }
        _modify {
            defer { print("Printed second") }
            print("Printed first")
            yield &value
            print("Not printed") // won’t be executed
        }
    }
}

func throwsOnPurpose(_ value: inout Int) throws {
    throw NSError(domain: "Counter", code: 1, userInfo: nil)
}

var counter = Counter()
try throwsOnPurpose(&counter.current)
Prints
Printed first
Printed second
Swift/ErrorType.swift:253: Fatal error: Error raised at top level

set vs _modify

If set and _modify are both available, _modify is automatically used for compound assignments or other in-place modifications, while _set is automatically used for simple assignments (example). If only one (set or _modify) is available it will be used for both operations.

set and _modify

import Foundation

struct Counter {
    private var value: Int = 0
    
    var current: Int {
        get { value }
        set { value = newValue }
        _modify { 
            yield &value 
        }
    }
}

var counter = Counter()
print(counter.current) // uses get
counter.current += 1   // uses _modify
counter.current = 1    // uses set

The compound assignments that trigger _modify are

  • Arithmetic compound operators: +=, -=, *=, /=, %=
  • Bitwise compound operators: &=, |=, ^=, <<=, >>=
  • For strings/arrays: +=, .append(), .insert(), .remove(), and similar mutating methods
  • For dictionaries/sets: subscript with compound assignment (+=), .merge(), union/intersection operators like .formUnion(), .formIntersection()

Word of caution

_modify and yield were introduced in the pitch Modify Accessors in 2019. As of dec 2024, _modify still uses an underscore prefix. The pitch author says

Underscored attributes are not source stable and that means you need to deal with the consequences of them not being source stable

But if you still decide to use it, know that it continues to be added in Apple code, and it would be easy to replace.