|
| 1 | +# Feature name |
| 2 | + |
| 3 | +* Proposal: [SE-NNNN](NNNN-weak-let.md) |
| 4 | +* Authors: [Mykola Pokhylets](https://github.com/nickolas-pohilets) |
| 5 | +* Review Manager: TBD |
| 6 | +* Status: **Awaiting implementation** |
| 7 | +* Implementation: [swiftlang/swift#80440](https://github.com/swiftlang/swift/pull/80440) |
| 8 | +* Upcoming Feature Flag: `WeakLet` |
| 9 | +* Review: ([discussion](https://forums.swift.org/t/weak-captures-in-sendable-sending-closures/78498)) |
| 10 | + |
| 11 | +## Introduction |
| 12 | + |
| 13 | +Currently Swift requires weak stored variables to be mutable. |
| 14 | +This restriction is rather artificial, and causes friction with sendability checking. |
| 15 | + |
| 16 | +## Motivation |
| 17 | + |
| 18 | +Currently swift classes with weak stored properties cannot be `Sendable`, |
| 19 | +because weak properties have to be mutable, and mutable properties are |
| 20 | +not allowed in `Sendable` classes. |
| 21 | + |
| 22 | +Similarly, closures with `weak` captures cannot be `@Sendable`, |
| 23 | +because such captures are implicitly made mutable. |
| 24 | + |
| 25 | +Usually developers are not aware of this implicit mutability and have no intention to modify the captured variable. |
| 26 | +Implicit mutability of weak captures is inconsistent with `unowned` or default captures. |
| 27 | + |
| 28 | +Wrapping weak reference into a single-field struct, allows stored properties and captures to be immutable. |
| 29 | + |
| 30 | +```swift |
| 31 | +final class C: Sendable {} |
| 32 | + |
| 33 | +struct WeakRef { |
| 34 | + weak var ref: C? |
| 35 | +} |
| 36 | + |
| 37 | +final class User: Sendable { |
| 38 | + weak let ref1: C? // error: 'weak' must be a mutable variable, because it may change at runtime |
| 39 | + let ref2: WeakRef // ok |
| 40 | +} |
| 41 | + |
| 42 | +func makeClosure() -> @Sendable () -> Void { |
| 43 | + let c = C() |
| 44 | + return { [weak c] in |
| 45 | + c?.foo() // error: reference to captured var 'c' in concurrently-executing code |
| 46 | + c = nil // nobody does this |
| 47 | + } |
| 48 | + return { [c = WeakRef(ref: c)] in |
| 49 | + c.ref?.foo() // ok |
| 50 | + } |
| 51 | +} |
| 52 | +``` |
| 53 | + |
| 54 | +Existence of this workaround shows that ban on `weak let` variables is artificial, and can be lifted. |
| 55 | + |
| 56 | +Note that resetting weak references on object destruction is different from regular variable modification. |
| 57 | +Resetting on destruction is implemented in a thread-safe manner, and can safely coexist with concurrent reads or writes. |
| 58 | +But regular writing to a variable requires exclusive access to that memory location. |
| 59 | + |
| 60 | +## Proposed solution |
| 61 | + |
| 62 | +Allow `weak let` declarations for local variables and stored properties. |
| 63 | + |
| 64 | +Proposal maintains status quo regarding use of `weak` on function arguments and computed properties: |
| 65 | +* there is no valid syntax to indicate that function argument is a weak reference; |
| 66 | +* `weak` on computed properties is allowed, but has not effect. |
| 67 | + |
| 68 | +Weak captures are immutable under this proposal. If mutable capture is desired, |
| 69 | +mutable variable need to be explicit declared and captured. |
| 70 | + |
| 71 | +```swift |
| 72 | +func makeClosure() -> @Sendable () -> Void { |
| 73 | + let c = C() |
| 74 | + // Closure is @Sendable |
| 75 | + return { [weak c] in |
| 76 | + c?.foo() |
| 77 | + c = nil // error: cannot assign to value: 'c' is an immutable capture |
| 78 | + } |
| 79 | + |
| 80 | + weak var explicitlyMutable: C? = c |
| 81 | + // Closure cannot be @Sendable anymore |
| 82 | + return { |
| 83 | + explicitlyMutable?.foo() |
| 84 | + explicitlyMutable = nil // but assigned is ok |
| 85 | + } |
| 86 | +} |
| 87 | +``` |
| 88 | + |
| 89 | +## Source compatibility |
| 90 | + |
| 91 | +Allowing `weak let` bindings is a source-compatible change |
| 92 | +that makes previously invalid code valid. |
| 93 | + |
| 94 | +Treating weak captures as immutable is a source-breaking change. |
| 95 | +Any code that attempts to write to the capture will stop compiling. |
| 96 | +The overall amount of such code is expected to be small. |
| 97 | + |
| 98 | +## ABI compatibility |
| 99 | + |
| 100 | +This is an ABI-compatible change. |
| 101 | + |
| 102 | +## Implications on adoption |
| 103 | + |
| 104 | +This feature can be freely adopted and un-adopted in source |
| 105 | +code with no deployment constraints and without affecting source or ABI |
| 106 | +compatibility. |
0 commit comments