Sending closure

is the code below a bug?
Because it send the nonSendable to the other region.

public final class NonSendable {
  var value = 0
}

func f4(_ closure: sending @escaping () async -> Void) {
  Task.detached {
    await closure()
  }
}

do {
    let nonSendable = NonSendable()
    f4 {
      nonSendable.value = 0
        print(nonSendable.value)
      
    }
    nonSendable.value = 0
    print(nonSendable.value)
}

I have no issue with the compiler.

Swift version 6.0.1 (swift-6.0.1-RELEASE) Target: aarch64-unknown-linux-gnu

perhaps, but i think it's likely an artifact of top-level code, which has a number of its own discrepancies & quirks. if you replace the do block with a function, the expected concurrency errors appear (on the nightly toolchains at least). i found discussion about a similar scenario in this github issue.

I'd say it's definitely a bug. I can do this:

do {
  let nonSendable = NonSendable()
  f4 {
    nonSendable.value = 1
    await Task.yield()
    print(nonSendable.value, "= 1")
  }
  f4 {
    nonSendable.value = 2
    await Task.yield()
    print(nonSendable.value, "= 2")
  }
}

and pretty consistently print "2 = 1".

There is a logical race here, but I do not think this is a bug (yet!). I believe the problem is this:

func f4(_ closure: sending @escaping () async -> Void) {
  Task.detached {
   // "closure" here was formed on and will be executed on the MainActor
    await closure()
  }
}

But this is a detached task

You are correct, the synchronous code executed within the detached task will be done on a background thread. But closure is not synchronous, and that await will move execution back to the MainActor.

Further, because the closure is not @Sendable, it can never be moved from the MainActor, where it is formed, to the background. Only @Sendable closures can cross actor boundaries like that.

Should it be reported?

Wait let me revise my response! The closure is non-isolated and async, so its body will not be MainActor-isolated. Sorry about the misunderstanding!

Yeah, it does seem like this is allowing you to sneak a non-Sendable across boundaries in a way that should not be allowed. I definitely think this is worth a bug.

(but also, @jamieQ is right, this is correctly caught in non-top-level code)

what he said. I had known that it has no issue if it is not in the top level. So, it is still a bug for me.

1 Like