Data race when capturing let assigned object in @Sendable closure

SE-0302 defines a few compiler checking rules, first 2 being:

  1. [....] . Any captures must also conform to Sendable .
  2. Closures that have @Sendable function type can only use by-value captures. Captures of immutable values introduced by let are implicitly by-value

In result the code below is allowed

class C {
   var i = 0
}

let c = C() 
var i = 0

for _ in 1...1000 {
   // Task's operation is defined as @Sendable @escaping () async -> Success
   Task {
      //i = i + 1 //ERROR: referencing and mutating not allowed 
      c.i = c.i + 1  // OK!
   }
}

which is quite basic example of race condition.

So it seems like it's not allowed to capture reference to variable i (that's OK), but it's perfectly allowed to capture constant value c, being reference to not Sendable class C (the fact, compiler is aware of). It seems quite strange to me.

Shouldn't such potential problems be captured by compiler as errors (or at least warnings)?


I'm not freqent reader of this forum, so sorry if I'm repeaiting topic similiar to other(s) already discussed which I couldn't find.
And yes, I know that using actor with incrementer instead of class would solve the problem, but the threat of using class may not be always as visible as in this example.

Full Sendable checking is not enabled in the Swift 5.5 compiler. If you want better diagnostics, trying the new Xcode 13.3 beta with Swift 5.6, or one of the 5.6 or main toolchains, which have much more checking enabled by default.

I'll try it out, thx. I'm still not sure what to expect - is it an error at all?. Reading SE-0302 literally it seems like let constant should be accepted by @Sendable clousure ( values introduced by let are implicitly by-value).

The [SE-0302] was implemented (Swift 5.7), so you have to wait for version 5.7.

In Swift 6 this is likely to be an error, in Swift 5.7 and a recent Xcode 14 beta build this should emit a soft-error aka. warning. Generally speaking you cannot capture a non-sendable instance into a sendable context (this also applies to an init on an actor type).

Your code:

  • Your code seems to compile fine in Swift 5.7

Modified code:

When I modify your code to embed inside a function f1 then I can see the warnings

func f1() {
    let c = C()
    var i = 0
    
    for _ in 1...1000 {
        // Task's operation is defined as @Sendable @escaping () async -> Success
        Task {
            i = i + 1 //Warning: Reference to captured var 'i' in concurrently-executing code; this is an error in Swift 6
            c.i = c.i + 1  //Warning: Capture of 'c' with non-sendable type 'C' in a `@Sendable` closure
        }
    }
}

Environment:

  • Xcode: Version 14.0 beta 3 (14A5270f)
  • macOS: 13.0 Beta (22A5295i)
  • Swift: version 5.7 (swiftlang-5.7.0.120.1 clang-1400.0.28.1)
  • Build Setting: Strict Concurrency Checking - Complete

Doubt

  • Even I have noticed some strange (I don't know why) behaviour with global variables.
  • May be someone could explain why this happens with global variables but works as expected when working within a function.