Converting function value of type '@MainActor (..., ...) -> Bool' to '(..., ...) throws -> Bool' loses global actor 'MainActor'

I have the following code, which can be pasted right into an empty project:

struct Product: Decodable, Identifiable {
    let id: Int
    let title: String
}

@MainActor class ViewModel: ObservableObject {
    @Published var products: [Product] = []
    
    func refresh() async {
        do {
            let products = try await {
                try await Task.sleep(for: Duration.seconds(2)) // Simulate network call
                return [Product(id: 1, title: "iPhone")]
            }()
            self.products = products.sorted(by: self.sortClosure(_:_:))
        } catch {
            print(error.localizedDescription)
        }
    }
    
    private func sortClosure(_ lhs: Product, _ rhs: Product) -> Bool {
        lhs.id < rhs.id
    }
}

struct ContentView: View {
    @StateObject private var viewModel = ViewModel()
    
    var body: some View {
        List(self.viewModel.products) { product in
            Text(product.title)
        }
        Button("Refresh") {
            Task {
                await self.viewModel.refresh()
            }
        }
    }
}

The following line:

self.products = products.sorted(by: self.sortClosure(_:_:))

makes the compiler emit the following warning:

Converting function value of type '@MainActor (Product, Product) -> Bool' to '(Product, Product) throws -> Bool' loses global actor 'MainActor'

I could add nonisolated to sortClosure(_:_:)) but that seems silly. I also tried conforming the struct Product to the Sendable protocol but that also does not shut up the warning.

Frankly, I just don't understand why the following works:

self.products = products.sorted(by: { $0.id < $1.id })

and the following would generate the above-mentioned warning:

self.products = products.sorted(by: self.sortClosure(_:_:))

What am I missing?

Why does this seem silly? It seems to me that that’s exactly the intent you want to express—you’ve marked the enclosing type as @MainActor so all members will implicitly be @MainActor unless you opt them out.

The version you’ve written with the inline closure doesn’t produce a warning because the closure doesn’t inherit the @MainActor isolation from the enclosing type.

4 Likes

The version you’ve written with the inline closure doesn’t produce a warning because the closure doesn’t inherit the @MainActor isolation from the enclosing type.

Thanks so much, I hadn't realized that!