Skip to content

Commit 5055d9a

Browse files
Proposal to unban weak let bindings
1 parent 3cab530 commit 5055d9a

File tree

1 file changed

+106
-0
lines changed

1 file changed

+106
-0
lines changed

proposals/NNNN-weak-let.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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

Comments
 (0)