Skip to content

Commit fcbbec7

Browse files
Example Executable (#23)
* Example Executable This is a concept of an example-hosting executable. It includes some static variable usage to help get a feel for how something like this could be structured. I really wanted to construct a `Package.swift` that could build the executable with three possible configurations: - Swift 5 compiler (5 mode) - Swift 6 compiler (5 mode) - Swift 6 compiler (6 mode) This turned out to be pretty tricky to pull off, but I have it working. SPM will not let you use the same path for multiple targets, but I also was able to avoid duplicating the code by using symlinks. * Boundary crossing examples * Package better-suited to examples * More isolation crossing examples * remove some test code that snuck in * Correct implicit Sendable conformance comment * Highlight RBI * Use 6.0 tools * whitespace *sobs* * add todo about withCheckedContinuation usage * Rmove use of 'globally isolated' * Improve wording of not-Sendable-when-public * move indentation to spaces
1 parent f50feb9 commit fcbbec7

14 files changed

+275
-78
lines changed

Package.swift

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version: 5.10
1+
// swift-tools-version: 6.0
22

33
import PackageDescription
44

@@ -14,31 +14,31 @@ let package = Package(
1414
],
1515
products: [
1616
.library(
17-
name: "FullyMigratedModule",
18-
targets: [
19-
"FullyMigratedModule",
20-
"MigrationInProgressModule",
21-
]
17+
name: "Library",
18+
targets: ["Library"]
2219
),
20+
.executable(name: "swift5_examples", targets: ["Swift5Examples"]),
21+
.executable(name: "swift6_examples", targets: ["Swift6Examples"]),
2322
],
2423
targets: [
2524
.target(
26-
name: "FullyMigratedModule"
25+
name: "Library"
2726
),
2827
.target(
29-
name: "MigrationInProgressModule",
30-
dependencies: ["FullyMigratedModule"]
28+
name: "ObjCLibrary",
29+
publicHeadersPath: "."
30+
),
31+
.executableTarget(
32+
name: "Swift5Examples",
33+
dependencies: ["Library"],
34+
swiftSettings: [
35+
.swiftLanguageVersion(.v5)
36+
]
3137
),
38+
.executableTarget(
39+
name: "Swift6Examples",
40+
dependencies: ["Library"]
41+
)
3242
],
33-
swiftLanguageVersions: [.v5]
43+
swiftLanguageVersions: [.v6]
3444
)
35-
36-
let swiftSettings: [SwiftSetting] = [
37-
.enableExperimentalFeature("StrictConcurrency")
38-
]
39-
40-
for target in package.targets {
41-
var settings = target.swiftSettings ?? []
42-
settings.append(contentsOf: swiftSettings)
43-
target.swiftSettings = settings
44-
}

[email protected]

Lines changed: 0 additions & 43 deletions
This file was deleted.

Sources/Examples/Boundaries.swift

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import Library
2+
3+
// MARK: Core Example Problem
4+
5+
/// A `MainActor`-isolated function that accepts non-`Sendable` parameters.
6+
@MainActor
7+
func applyBackground(_ color: ColorComponents) {
8+
}
9+
10+
#if swift(<6.0)
11+
/// A non-isolated function that accepts non-`Sendable` parameters.
12+
func updateStyle(backgroundColor: ColorComponents) async {
13+
// the `backgroundColor` parameter is being moved from the
14+
// non-isolated domain to the `MainActor` here.
15+
//
16+
// Swift 5 Warning: passing argument of non-sendable type 'ColorComponents' into main actor-isolated context may introduce data races
17+
// Swift 6 Error: sending 'backgroundColor' risks causing data races
18+
await applyBackground(backgroundColor)
19+
}
20+
#endif
21+
22+
// MARK: Latent Isolation
23+
24+
/// MainActor-isolated function that accepts non-`Sendable` parameters.
25+
@MainActor
26+
func isolatedFunction_updateStyle(backgroundColor: ColorComponents) async {
27+
// This is safe because backgroundColor cannot change domains. It also
28+
// now no longer necessary to await the call to `applyBackground`.
29+
applyBackground(backgroundColor)
30+
}
31+
32+
// MARK: Explicit Sendable
33+
34+
/// An overload used by `sendable_updateStyle` to match types.
35+
@MainActor
36+
func applyBackground(_ color: SendableColorComponents) {
37+
}
38+
39+
/// The Sendable variant is safe to pass across isolation domains.
40+
func sendable_updateStyle(backgroundColor: SendableColorComponents) async {
41+
await applyBackground(backgroundColor)
42+
}
43+
44+
// MARK: Computed Value
45+
46+
/// A Sendable function is used to compute the value in a different isolation domain.
47+
func computedValue_updateStyle(using backgroundColorProvider: @Sendable () -> ColorComponents) async {
48+
#if swift(<6.0)
49+
// pass backgroundColorProvider into the MainActor here
50+
await MainActor.run {
51+
// invoke it in this domain to actually create the needed value
52+
let components = backgroundColorProvider()
53+
applyBackground(components)
54+
}
55+
#else
56+
// The Swift 6 compiler can automatically determine this value is
57+
// being transferred in a safe way
58+
let components = backgroundColorProvider()
59+
await applyBackground(components)
60+
#endif
61+
}
62+
63+
// MARK: Global Isolation
64+
/// An overload used by `globalActorIsolated_updateStyle` to match types.
65+
@MainActor
66+
func applyBackground(_ color: GlobalActorIsolatedColorComponents) {
67+
}
68+
69+
/// MainActor-isolated function that accepts non-`Sendable` parameters.
70+
@MainActor
71+
func globalActorIsolated_updateStyle(backgroundColor: GlobalActorIsolatedColorComponents) async {
72+
// This is safe because backgroundColor cannot change domains. It also
73+
// now no longer necessary to await the call to `applyBackground`.
74+
applyBackground(backgroundColor)
75+
}
76+
77+
func exerciseBoundaryCrossingExamples() async {
78+
print("Isolation Boundary Crossing Examples")
79+
80+
#if swift(<6.0)
81+
print(" - updateStyle(backgroundColor:) passing its argument unsafely")
82+
#endif
83+
84+
print(" - using ColorComponents only from the main actor")
85+
let t1 = Task { @MainActor in
86+
let components = ColorComponents()
87+
88+
await isolatedFunction_updateStyle(backgroundColor: components)
89+
}
90+
91+
await t1.value
92+
93+
print(" - using preconcurrency_updateStyle to deal with non-Sendable argument")
94+
95+
print(" - using a Sendable closure to defer creation")
96+
await computedValue_updateStyle(using: {
97+
ColorComponents()
98+
})
99+
100+
print(" - using a globally-isolated type")
101+
let components = await GlobalActorIsolatedColorComponents()
102+
103+
await globalActorIsolated_updateStyle(backgroundColor: components)
104+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Dispatch
2+
3+
extension DispatchQueue {
4+
/// Returns once any pending work has been completed.
5+
func pendingWorkComplete() async {
6+
// TODO: update to withCheckedContinuation https://github.com/apple/swift/issues/74206
7+
await withUnsafeContinuation { continuation in
8+
self.async(flags: .barrier) {
9+
continuation.resume()
10+
}
11+
}
12+
}
13+
}

Sources/Examples/Globals.swift

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import Dispatch
2+
3+
#if swift(<6.0)
4+
/// An unsafe global variable.
5+
///
6+
/// See swift-6-concurrency-migration-guide/commonproblems/#Sendable-Types
7+
var supportedStyleCount = 42
8+
#endif
9+
10+
/// Version of `supportedStyleCount` that uses global-actor isolation.
11+
@MainActor
12+
var globallyIsolated_supportedStyleCount = 42
13+
14+
/// Version of `supportedStyleCount` that uses immutability.
15+
let constant_supportedStyleCount = 42
16+
17+
/// Version of `supportedStyleCount` that uses manual synchronization via `sharedQueue`
18+
nonisolated(unsafe) var queueProtected_supportedStyleCount = 42
19+
20+
/// A non-isolated async function used to exercise all of the global mutable state examples.
21+
func exerciseGlobalExamples() async {
22+
print("Global Variable Examples")
23+
#if swift(<6.0)
24+
// Here is how we access `supportedStyleCount` concurrently in an unsafe way
25+
for _ in 0..<10 {
26+
DispatchQueue.global().async {
27+
supportedStyleCount += 1
28+
}
29+
}
30+
31+
print(" - accessing supportedStyleCount unsafely:", supportedStyleCount)
32+
33+
await DispatchQueue.global().pendingWorkComplete()
34+
#endif
35+
36+
print(" - accessing globallyIsolated_supportedStyleCount")
37+
// establish a MainActor context to access the globally-isolated version
38+
await MainActor.run {
39+
globallyIsolated_supportedStyleCount += 1
40+
}
41+
42+
// freely access the immutable version from any isolation domain
43+
print(" - accessing constant_supportedStyleCount when non-isolated: ", constant_supportedStyleCount)
44+
45+
await MainActor.run {
46+
print(" - accessing constant_supportedStyleCount from MainActor: ", constant_supportedStyleCount)
47+
}
48+
49+
// access the manually-synchronized version... carefully
50+
manualSerialQueue.async {
51+
queueProtected_supportedStyleCount += 1
52+
}
53+
54+
manualSerialQueue.async {
55+
print(" - accessing queueProtected_supportedStyleCount: ", queueProtected_supportedStyleCount)
56+
}
57+
58+
await manualSerialQueue.pendingWorkComplete()
59+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@preconcurrency import Library
2+
3+
/// A non-isolated function that accepts non-`Sendable` parameters.
4+
func preconcurrency_updateStyle(backgroundColor: ColorComponents) async {
5+
// Swift 5: no diagnostics
6+
// Swift 6 Warning: sending 'backgroundColor' risks causing data races
7+
await applyBackground(backgroundColor)
8+
}

Sources/Examples/main.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import Dispatch
2+
3+
/// A Serial queue uses for manual synchronization
4+
let manualSerialQueue = DispatchQueue(label: "com.apple.SwiftMigrationGuide")
5+
6+
// Note: top-level code provides an asynchronous MainActor-isolated context
7+
await exerciseGlobalExamples()
8+
await exerciseBoundaryCrossingExamples()

Sources/FullyMigratedModule/FullyMigratedModule.swift

Lines changed: 0 additions & 8 deletions
This file was deleted.

Sources/Library/Library.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import Foundation
2+
3+
/// An example of a struct with only `Sendable` properties.
4+
///
5+
/// This type is **not** Sendable because it is public. If we want a public type to be `Sendable`, we must annotate it explicitly.
6+
public struct ColorComponents {
7+
public let red: Float
8+
public let green: Float
9+
public let blue: Float
10+
11+
public init(red: Float, green: Float, blue: Float) {
12+
self.red = red
13+
self.green = green
14+
self.blue = blue
15+
}
16+
17+
public init() {
18+
self.red = 1.0
19+
self.green = 1.0
20+
self.blue = 1.0
21+
}
22+
}
23+
24+
/// Explicitly-Sendable variant of `ColorComponents`.
25+
public struct SendableColorComponents : Sendable {
26+
public let red: Float = 1.0
27+
public let green: Float = 1.0
28+
public let blue: Float = 1.0
29+
30+
public init() {}
31+
}
32+
33+
@MainActor
34+
public struct GlobalActorIsolatedColorComponents : Sendable {
35+
public let red: Float = 1.0
36+
public let green: Float = 1.0
37+
public let blue: Float = 1.0
38+
39+
public init() {}
40+
}

Sources/MigrationInProgressModule/MigrationInProgressModule.swift

Lines changed: 0 additions & 7 deletions
This file was deleted.

Sources/ObjCLibrary/ObjCLibrary.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
@import Foundation;
2+
3+
@interface OCMPattern : NSObject
4+
5+
@end
6+
7+
NS_SWIFT_UI_ACTOR
8+
@interface PCMPatternStore : NSObject
9+
10+
@end

Sources/ObjCLibrary/ObjCLibrary.m

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#import <Foundation/Foundation.h>
2+
3+
#import "ObjCLibrary.h"
4+
5+
@implementation OCMPattern
6+
7+
@end
8+
9+
@implementation PCMPatternStore
10+
11+
@end

Sources/Swift5Examples

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Examples

Sources/Swift6Examples

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Examples

0 commit comments

Comments
 (0)