Skip to content

Commit ced6843

Browse files
committed
[Educational Notes] Add an explanation for the unsafe global variable
errors.
1 parent a0e05f7 commit ced6843

File tree

2 files changed

+62
-0
lines changed

2 files changed

+62
-0
lines changed

include/swift/AST/EducationalNotes.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ EDUCATIONAL_NOTES(regionbasedisolation_named_send_yields_race,
8787
"sending-risks-data-race.md")
8888
EDUCATIONAL_NOTES(regionbasedisolation_type_send_yields_race,
8989
"sending-risks-data-race.md")
90+
EDUCATIONAL_NOTES(shared_mutable_state_decl,
91+
"mutable-global-variable.md")
92+
EDUCATIONAL_NOTES(shared_immutable_state_decl,
93+
"mutable-global-variable.md")
9094

9195
EDUCATIONAL_NOTES(error_in_swift_lang_mode,
9296
"error-in-future-swift-version.md")
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Unsafe mutable global and static variables
2+
3+
Concurrency checking prohibits mutable global and static variables that are `nonisolated` because they can be accessed from arbitrary concurrency domains at once and lead to data races.
4+
5+
For example:
6+
7+
```swift
8+
struct Constants {
9+
static var value = 10
10+
}
11+
```
12+
13+
Building this code with complete concurrency checking will point out the unsafe static variable:
14+
15+
```
16+
| struct Constants {
17+
| static var value = 10
18+
| |- error: static property 'value' is not concurrency-safe because it is nonisolated global shared mutable state
19+
| |- note: convert 'value' to a 'let' constant to make 'Sendable' shared state immutable
20+
| |- note: add '@MainActor' to make static property 'value' part of global actor 'MainActor'
21+
| `- note: disable concurrency-safety checks if accesses are protected by an external synchronization mechanism
22+
```
23+
24+
If the type of the variable conforms to `Sendable` and the value is never changed, a common fix is to change the `var` to a `let` to make the state immutable. Immutable state is safe to access concurrently!
25+
26+
If you carefully access the global variable in a way that cannot cause data races, such as by wrapping all accesses in an external synchronization mechanism like a lock or a dispatch queue, you can apply `nonisolated(unsafe)` to opt out of concurrency checking:
27+
28+
```swift
29+
nonisolated(unsafe) static var value = 10
30+
```
31+
32+
Now consider a static variable with a type that does not conform to `Sendable`:
33+
34+
```swift
35+
class MyModel {
36+
static let shared = MyModel()
37+
38+
// mutable state
39+
}
40+
```
41+
42+
This code is also diagnosed under complete concurrency checking. Even though the `shared` variable is a `let` constant, the `MyModel` type is not `Sendable`, so it could have mutable stored properties. A common fix in this case is to isolate the variable to the main actor:
43+
44+
```swift
45+
class MyModel {
46+
@MainActor
47+
static let shared = MyModel()
48+
}
49+
```
50+
51+
Alternatively, isolate the `MyModel` class to the main actor, which will also make the type `Sendable` because the main actor protects access to all mutable state:
52+
53+
```swift
54+
@MainActor
55+
class MyModel {
56+
static let shared = MyModel()
57+
}
58+
```

0 commit comments

Comments
 (0)