I'd like to understand why this program behaves as it does and why there are no concurrency-related warnings or errors. This is using the Swift 6.0 that comes with Xcode 16.0 Beta 4 (16A521). Any insights would be appreciated.
% swift --version
swift-driver version: 1.112.3 Apple Swift version 6.0 (swiftlang-6.0.0.6.8 clang-1600.0.23.1)
Target: arm64-apple-macosx14.0
% cat > test.swift
final class C {
func foo(_ label: String) {
enum S {
static var count = 0
}
print(label, "| count:", S.count)
S.count += 1
}
}
func test() async throws {
let c = C()
Task {
c.foo("A")
}
c.foo("B")
c.foo("C")
try await Task.sleep(for: .seconds(1))
}
for _ in 0..<4 {
try! await test()
print("--")
}
% swiftc test.swift && ./test
A | count: 0
B | count: 0
C | count: 2
--
B | count: 3
C | count: 4
A | count: 5
--
B | count: 6
C | count: 7
A | count: 6
--
B | count: 9
C | count: 10
A | count: 9
--
For reference, here's what happens when changing `class` to `actor`.
% cat > test.swift
final actor C {
func foo(_ label: String) {
enum S {
static var count = 0
}
print(label, "| count:", S.count)
S.count += 1
}
}
func test() async throws {
let c = C()
Task {
await c.foo("A")
}
await c.foo("B")
await c.foo("C")
try await Task.sleep(for: .seconds(1))
}
for _ in 0..<4 {
try! await test()
print("--")
}
% swiftc test.swift && ./test
B | count: 0
A | count: 1
C | count: 2
--
B | count: 3
A | count: 4
C | count: 5
--
B | count: 6
A | count: 7
C | count: 8
--
B | count: 9
A | count: 10
C | count: 11
--
You compile it in Swift 5 mode, not 6, and with any concurrency checks turned on whatsoever. If you run it with swiftc test.swift -swift-version 6 it won't compile, as it should.
-swift-version is a bit misguiding and there is an ongoing thread about its renaming. It is a mode, that guards source-breaking changes that come with 6th version, so that code can be gradually migrated, and even with latest compiler defaults to pre-6 behaviour when it comes to concurrency checks.
You can use SPM and run your test code as part of a package with swift-tools-version: 6.0 set in Package.swift, then it will compile everything in Swift 6 mode by default.
I can’t recall such major breaking changes, so that’s pretty much the first time I think. It's just that adopting new concurrency might be not easy for projects with full checks on. Having defaults to throw potentially hundreds of errors might be off-putting on adoption.
These feature options guard some of concurrency checks, so that project can turn them on gradually, adopting it step-by-step. And Xcode behaviour I'd expect to remain mostly the same as it in betas on that.