|
| 1 | +# Captures in a `@Sendable` closure |
| 2 | + |
| 3 | +`@Sendable` closures can be called multiple times concurrently, so any captured values must also be safe to access concurrently. To prevent data races, the compiler prevents capturing mutable values in a `@Sendable` closure. |
| 4 | + |
| 5 | +For example: |
| 6 | + |
| 7 | +```swift |
| 8 | +func callConcurrently( |
| 9 | + _ closure: @escaping @Sendable () -> Void |
| 10 | +) { ... } |
| 11 | + |
| 12 | +func capture() { |
| 13 | + var result = 0 |
| 14 | + result += 1 |
| 15 | + |
| 16 | + callConcurrently { |
| 17 | + print(result) |
| 18 | + } |
| 19 | +} |
| 20 | +``` |
| 21 | + |
| 22 | +The compiler diagnoses the capture of `result` in a `@Sendable` closure: |
| 23 | + |
| 24 | +``` |
| 25 | +| callConcurrently { |
| 26 | +| print(result) |
| 27 | +| `- error: reference to captured var 'result' in concurrently-executing code |
| 28 | +| } |
| 29 | +| } |
| 30 | +``` |
| 31 | + |
| 32 | +Because the closure is marked `@Sendable`, the implementation of `callConcurrently` can call `closure` multiple times concurrently. For example, multiple child tasks within a task group can call `closure` concurrently: |
| 33 | + |
| 34 | +```swift |
| 35 | +func callConcurrently( |
| 36 | + _ closure: @escaping @Sendable () -> Void |
| 37 | +) { |
| 38 | + Task { |
| 39 | + await withDiscardingTaskGroup { group in |
| 40 | + for _ in 0..<10 { |
| 41 | + group.addTask { |
| 42 | + closure() |
| 43 | + } |
| 44 | + } |
| 45 | + } |
| 46 | + } |
| 47 | +} |
| 48 | +``` |
| 49 | + |
| 50 | +If the type of the capture is `Sendable` and the closure only needs the value of the variable at the point of capture, resolve the error by explicitly capturing the variable by value in the closure's capture list: |
| 51 | + |
| 52 | +```swift |
| 53 | +func capture() { |
| 54 | + var result = 0 |
| 55 | + result += 1 |
| 56 | + |
| 57 | + callConcurrently { [result] in |
| 58 | + print(result) |
| 59 | + } |
| 60 | +} |
| 61 | +``` |
| 62 | + |
| 63 | +This strategy does not apply to captures with non-`Sendable` type. Consider the following example: |
| 64 | + |
| 65 | +```swift |
| 66 | +class MyModel { |
| 67 | + func log() { ... } |
| 68 | +} |
| 69 | + |
| 70 | +func capture(model: MyModel) async { |
| 71 | + callConcurrently { |
| 72 | + model.log() |
| 73 | + } |
| 74 | +} |
| 75 | +``` |
| 76 | + |
| 77 | +The compiler diagnoses the capture of `model` in a `@Sendable` closure: |
| 78 | + |
| 79 | +``` |
| 80 | +| func capture(model: MyModel) async { |
| 81 | +| callConcurrently { |
| 82 | +| model.log() |
| 83 | +| `- error: capture of 'model' with non-sendable type 'MyModel' in a '@Sendable' closure |
| 84 | +| } |
| 85 | +| } |
| 86 | +``` |
| 87 | + |
| 88 | +If a type with mutable state can be referenced concurrently, but all access to mutable state happens on the main actor, isolate the type to the main actor and mark the methods that don't access mutable state as `nonisolated`: |
| 89 | + |
| 90 | +```swift |
| 91 | +@MainActor |
| 92 | +class MyModel { |
| 93 | + nonisolated func log() { ... } |
| 94 | +} |
| 95 | + |
| 96 | +func capture(model: MyModel) async { |
| 97 | + callConcurrently { |
| 98 | + model.log() |
| 99 | + } |
| 100 | +} |
| 101 | +``` |
| 102 | + |
| 103 | +The compiler will guarantee that the implementation of `log` does not access any main actor state. |
| 104 | + |
| 105 | +If you manually ensure data-race safety, such as by using an external synchronization mechanism, you can use `nonisolated(unsafe)` to opt out of concurrency checking: |
| 106 | + |
| 107 | +```swift |
| 108 | +class MyModel { |
| 109 | + func log() { ... } |
| 110 | +} |
| 111 | + |
| 112 | +func capture(model: MyModel) async { |
| 113 | + nonisolated(unsafe) let model = model |
| 114 | + callConcurrently { |
| 115 | + model.log() |
| 116 | + } |
| 117 | +} |
| 118 | +``` |
0 commit comments