Why is != not sendable?

public static var whenDifferent: ShouldEmitValue<StateType> { .when(!=) }

causes

Converting non-sendable function value to '@Sendable (StateType, StateType) -> Bool' may introduce data races

same for ==

works fine with

public static var whenDifferent: ShouldEmitValue<StateType> { .when{ $0 != $1 } }

Swift concurrency really drives me crazy

public enum ShouldEmitValue<StateType>: Sendable {
    // private let evaluate: (StateType, StateType) -> Bool

    /// It will always emit changes, regardless of previous and new state
    case always

    /// It will never emit changes, regardless of previous and new state
    case never

    /// It's a custom-defined predicate, you'll be given old and new state, and must return a Bool indicating what you've decided from that change,
    /// being `true` when you want this change to be notified, or `false` when you want it to be ignored.
    case when(@Sendable (StateType, StateType) -> Bool)
}

extension ShouldEmitValue where StateType: Equatable {
    // Converting non-sendable function value to '@Sendable (StateType, StateType) -> Bool' may introduce data races
    public static var whenDifferent: ShouldEmitValue<StateType> { .when(!=) }
}

Var 'globalCounter' is not concurrency-safe because it is nonisolated global shared mutable state

Swift 6 does not allow you to do such stuff.

You can do something like

class NonSendableClass {
    var value: Int = 0
}

struct Foo: Equatable {
    let i: Int
    
    let nonSendable = NonSendableClass()
    
    @Sendable
    static func == (lhs: Self, rhs: Self) -> Bool {
        lhs.nonSendable.value += 1 // Data race!
        return lhs.i == rhs.i
    }
}

let equal: @Sendable (Foo, Foo) -> Bool = { $0 == $1 }

and it works. How can equal to be @Sendable?
It does not make sense at all.

If you're still using Swift 5, you may need the upcoming feature flag from this SE: swift-evolution/proposals/0418-inferring-sendable-for-methods.md at main · swiftlang/swift-evolution · GitHub

I am trying to switch to Swift 6.
Swift 5 is fine, it just shows a lot of warnings.

I think it's just an inference limitation.

When the compiler cannot infer a sendable closure, or when it cannot upgrade a free-form function to a sendable closure, there's no downside to explicit mark it. Just as you did in .when{ $0 != $1 }.

Though, I do think it's a good question to ask: whether we can treat any global functions to be automatically Sendable.
When SE-0302 came out, there wasn't much discussions about global variables, so it was pretty possible for global functions to capture unsafe global state. Now, we have SE-0412 fixing many holes, I believe it is legit to review the question again.

Trying for a while, I cannot come up with any counter-examples, maybe the experts in this area can share some thoughts? @hborla

1 Like