PAYATHON 2026

Swift isolated 参数为何没有强制其他参数遵循 Sendable?

数据小陈

结论

isolated BankAccount 会将 charge 函数隔离到传入的 BankAccount actor,让函数体可以直接访问该 actor 的状态。它不会因此将其他参数标记为 Sendable。非 Sendable 值能否传入,取决于调用是否跨越隔离域,以及编译器能否确认传递不会导致并发访问。

Swift 的 region-based isolation 允许编译器在满足安全条件时转移非 Sendable 值。例如,值位于独立区域,传递后也不会再从原隔离域访问。具体诊断会因 Swift 版本、语言模式和并发检查设置而异。题目没有提供这些信息,因此仅凭代码无法判断编译器为何没有报告。可参考 SE-0414:Region-Based Isolation 和 Swift 编译器的跨隔离域诊断说明。

这段代码里的隔离关系

charge 声明了 from bankAccount: isolated BankAccount,因此函数体在该 BankAccount 的 actor 隔离下执行,可以直接访问 withdraw 和 balance。调用处使用 await,因为执行需要切换到对应的 actor 隔离域。

AlternativeBankAccount 是普通 class,没有显式遵循 Sendable。这并不表示每次调用都必须将它声明为 Sendable。编译器可能会根据值所在的隔离区域和后续使用方式,判断是否可以安全转移。isolated 参数不会让 AlternativeBankAccount 变成线程安全类型,也不能保证所有编译设置下都不会出现诊断。

如何判断是否安全

题目中的 bankAccount2 在 @MainActor 方法 random 中创建,之后被捕获进 Task。日志显示这次运行分别打印了 main 线程和编号为 7 的线程,但线程编号无法说明 actor 隔离情况;线程和 actor executor 是两个不同的概念。

如果编译器接受这段代码,应结合所用的 Swift 版本和并发检查模式确认适用的规则。如果 AlternativeBankAccount 是可从多个隔离域共享的可变引用类型,即使没有警告,也不能据此认定共享访问是安全的。将对象放在明确的隔离域中,通常更容易表达预期。

例如,可以将账户建模为 actor:

actor AlternativeBankAccount {
    var balance: Double

    init(initialDeposit: Double) {
        balance = initialDeposit
    }

    func deposit(amount: Double) {
        balance += amount
    }
}

struct Charger {
    func charge(
        amount: Double,
        from bankAccount: isolated BankAccount,
        to otherAccount: AlternativeBankAccount
    ) async throws -> (Double, Double) {
        try bankAccount.withdraw(amount: amount)
        let newBalance = bankAccount.balance

        await otherAccount.deposit(amount: amount)
        let otherBalance = await otherAccount.balance
        return (newBalance, otherBalance)
    }
}

actor 引用可以安全地跨隔离域传递。访问另一个 actor 的隔离状态时,需要使用 await。这样,两个账户分别由各自的 actor 保护,但它们之间的操作仍不是一个不可分割的事务。

备注:内容仅供参考。