😳

Sendableじゃない型がactor boundaryを超える!?Swift 6からのRegion Based Isolaton

2024/07/07に公開

Swift 6からの新しい挙動として、Region Based Isolationというものがあります。
これはSendableじゃない型でもactor boundaryを超えることができるという変わった挙動をします。

今までだとどうなっていたのか

Swift 5環境でStrict Concurrencyを有効化すると以下のコードは警告が出ます。
どこで警告が出そうか注意深く見てみてください。

func updateView(view: ComplexLayoutView) async {
    // どのactorにも隔離されていない関数
    let layout = ComplexLayout.calculate()
    await view.apply(layout)
}

// Non Sendable
class ComplexLayout {
    // ...(レイアウト情報のプロパティ)

    static func calculate() -> ComplexLayout {
        //...(レイアウトの計算)
        return ComplexLayout()
    }
}

@MainActor
class ComplexLayoutView {
    func apply(_ layout: ComplexLayout) {
        // 引数の情報をもとにレイアウトを更新
    }
}

答えはここです。

func updateView(view: ComplexLayoutView) async {
    // どのactorにも隔離されていない関数
    let layout = ComplexLayout.calculate()
    await view.apply(layout) // ⚠️ Passing argument of non-sendable type ...
}

警告の内容は次の通りです。

Passing argument of non-sendable type 'ComplexLayout' into main actor-isolated context may introduce data races

つまり、どのactorにも隔離されていないupdateViewの中で作ったnon-sendableなlayoutが、MainActorに隔離されているComplextLayoutViewにも共有されており、data raceを引き起こす可能性があるというものです。

しかし、これは本当にdata raceを引き起こすのでしょうか?
data raceは複数のスレッドから同時にアクセスすることで発生します。
しかし上のupdateViewの中のコードでlayoutは作られた後にapply関数に渡してから、updateViewの中では一度も触られていません。
つまり、このlayoutの場合、MainActorに渡した後は他のどのactorからも触れることはなく、data raceを引き起こしません。

こういったケースのために導入されたのがRegion Based Isolationです。

Swift 6からはどうなるのか

上のコードは、警告がでずにビルドできます。

しかし、あくまでこれは同時にアクセスされないことが保証できる場合のみケースです。
次のようなコードはエラー(or 警告)がでます。

class ComplexLayout {
    // ..
    func printInformation() { ... }
} 

func updateView(view: ComplexLayoutView) async {
    let layout = ComplexLayout.calculate()
    await view.apply(layout) // ⚠️ Passing argument of non-sendable type ...
    layout.printInformation()
}

この場合MainActorにlayoutを渡した後、updateViewの中で再度layoutを触っているためです。
これは複数のスレッドから同時にアクセスされる可能性が残るため、コンパイラがそれを指摘します。

sendingキーワード

引数に対するsendingキーワード

上の例の場合、layoutを関数の中で作っていました。
これが引数から渡される場合、呼び出し元の関数で引き続きlayoutを触る可能性があるため、警告が出ます。

func updateView(view: ComplexLayoutView, with layout: ComplexLayout) async {
    await view.apply(layout) // ⚠️ Passing argument of non-sendable type ...
}

こういった場合、sendingキーワードを引数に足すことで回避できます。
このsendingは別のactorにパスされることを示したものです。
以下のコードは何も警告が出ません。

func updateView(view: ComplexLayoutView, with layout: sending ComplexLayout) async {
    await view.apply(layout)
}

func anotherFunction() async {
    let view = ComplexLayoutView()
    let layout = ComplexLayout.calculate()
    await updateView(view: view, layout: layout)
}

しかし、updateViewの後にlayoutにアクセスすると警告が出ます。
これは、updateViewの中で別のactorにlayoutを渡した後に引き続きlayout にアクセスしているからです。

func anotherFunction() async {
    let view = ComplexLayoutView()
    let layout = ComplexLayout.calculate()
    await updateView(view: view, layout: layout) // ⚠️ Sending 'layout' risks causing data races...
    layout.printInformation()
}

返り値に対するsendingキーワード

このsendingキーワードは関数の返り値の型にもつけることができます。
関数の返り値についていた場合、その返り値がどこからもアクセスされないことを意味します。

以下のcalculate関数は問題ありませんが、calculateWithCache関数は警告になります。
なぜなら、返り値のComplexLayoutは別のactorにパスされる可能性がありますが、それをcacheとしてキャプチャーしていて、他のスレッドから呼び出される可能性が残っているからです。

class ComplexLayoutLayout {
    static func calculate() -> sending ComplexLayout {
        ComplexLayout()
    }

    @FooActor private static var cache: ComplexLayout?

    @FooActor static func calculateWithCache() -> sending ComplexLayout {
        if let cache {
            return cache
        }
        let layout = ComplexLayout()
        cache = layout
        return layout
    } // ⚠️ Sending 'layout' risks causing data races...
}

Xcode 16 Beta 2での状況

残念ながら、この記事執筆時の最新であるXcode 16 Beta 2では、Taskに対してはこの機能が動かないようです。

そのため、以下のようなコードは警告が出てしまいます。

let layout = ComplexLayout.calculate()
Task { @MainActor in
    view.apply(layout)
}

ただ、どうやら少し前に、Taskに対してもこれが正しく動くようにするPRがマージされたらしいので、次のBetaやXcode 16の正式リリースには含まれることが期待できます。
https://github.com/swiftlang/swift/pull/74608

おわりに

Swift 6で有効化されるRegion Based Isolationというものを紹介しました。
これがどういう仕組みで動いてるのか、詳しい挙動についてはSE-414をご確認ください。
ただし複雑な内容な上、ページの中でも「開発者がその細かい挙動までを理解する必要はない」と述べられているため、無理に追う必要はないと思います。

参考

Discussion