Why does .task(function) give me a concurrency warning, but .task { await function() } doesn't?

I have a view like this below.

@MainActor
struct ContentView: View {
    @State private var text = "Old Value"
    
    var body: some View {
        Text(text)
            .task(function) // ⚠️ Converting non-sendable function value to '@Sendable () async -> Void' may introduce data races
    }
    
    func function() async {
        try? await Task.sleep(nanoseconds: 3_000_000_000)
        text = "New Value"
    }
}

In line 14, I get a concurrency warning Converting non-sendable function value to '@Sendable () async -> Void' may introduce data races. But the warning disappears if I change it to .task { await function() }. What is going on here? Isn't both .task { await function() } and .task(function) the same?

Can you please provide more information about how you're building this code, including the compiler flags that you're using? Have you enabled -enable-upcoming-feature InferSendableFromCaptures?

@hborla complete checking swift 5.10 without any flags. Warning is also in swift 6.0, too.

Thank you! I believe enabling the InferSendableFromCaptures upcoming feature will resolve the warning. If you're using Xcode 16, you can enable this in the build settings of your project in the Swift Compiler - Upcoming Features section by switching the setting for "Infer Sendable for Methods and Key-Path Literals" to "Yes".

EDIT: Note that this upcoming feature is new in Swift 6.0, so it is not available in Swift 5.10.

Oh, I thought functions automatically inferred Sendable like structs. But why .task { await function() } didn't give me any warnings?:thinking:

Hi @hborla,

I think that I am seeing the same issue using Swift 6 compiler mode with the Xcode 16 RC:

func a(operator: @Sendable (Int, Int) -> Bool) {
}
func b() {
    a(operator: { $0 == $1 }) // OK
    a(operator: ==) // Error: Converting non-sendable function value to '@Sendable (Int, Int) -> Bool' may introduce data races
}

I did try enabling the upcoming feature flag for the module where I use this pattern, but that unfortunately gives me multiple compiler errors:
Command SwiftCompile failed with a nonzero exit code

Unfortunately I can't really see more details in the Xcode build log.

Is it publicly available when an upcoming feature might become default?

It won’t be on by default until they feel like doing source breaking changes. Whether that’s Swift 7 or what, we don’t know yet.

InferSendableFromCaptures is enabled by default in the Swift 6 language mode. It's part of the data-race safety story.

This looks like a bug in the upcoming feature for unapplied operator functions.

5 Likes

So which features are enabled in Swift 6 mode and which aren’t? All of them? Xcode 16 doesn’t seem to make any distinction, and you can toggle them regardless of whether you’re in Swift 6 mode, so I figured they were always optional. Is the sets of features in each mode documented somewhere?

The complete set of upcoming features enabled by default in the Swift 6 language mode is documented in the migration guide.

4 Likes

There was a bug where unapplied operator references weren't handled correctly by InferSendableFromCaptures, I fixed that recently - [ConstraintSystem] InferSendableFromCaptures: Mark unapplied operator references as `@Sendable` by xedin · Pull Request #76136 · swiftlang/swift · GitHub

3 Likes

Ah, I see, some of those rules aren't surfaced in Xcode's UI. However, some are, like InferSendableFromCaptures. Is that difference meaningful in any way? For this particular rule I guess we just enable it manually?

No, I think it's just a bug if some of the upcoming features aren't surfaced in Xcode build settings. Thank you for pointing this out, I'll file a feedback report.

Yes, for any upcoming feature that doesn't have a check box in the dedicated section in Xcode build settings, you can always enable it via -enable-upcoming-feature X in "Other Swift Flags".

2 Likes