参照
提案概要
- SE-0414 Region based isolationを拡張する提案
- ある関数内の引数や返り値が Non-Sendableな値である場合に、
sendingキーワードを付与することで、その値が、安全にActor境界を超えることができるように、コンパイラによるデータ競合チェックを厳格化する
なぜ必要か
- Region based isolationが適用された状態(Swift5モードで Region based isolartionが有効化されている状態)では、以下コードで発生しているデータ競合リスクをコンパイル時点で検知できない問題がある
@MainActor var mainActorState: NonSendable?
nonisolated func test() async {
let ns = await withCheckedContinuation { continuation in
Task { @MainActor in
let ns = NonSendable()
// Oh no! 'NonSendable' is passed from the main actor to a
// nonisolated context here!
continuation.resume(returning: ns)
// Save 'ns' to main actor state for concurrent access later on
mainActorState = ns
}
}
// 'ns' and 'mainActorState' are now the same non-Sendable value;
// concurrent access is possible!
ns.mutate()
}- 今回の提案により、使えるようになる
sendingキーワードは、withCheckedContinuation apiにも付与される(付与されたことをXcode16RCで確認済み)
public struct CheckedContinuation<T, E: Error>: Sendable {
public func resume(returning value: sending T)
}
public func withCheckedContinuation<T>(
function: String = #function,
_ body: (CheckedContinuation<T, Never>) -> Void
) async -> sending T- それにより上記の例がエラーを適切に検知できるようになる
どのように使うか
- Non-Sendableな値を、Actor境界の外に渡したい場合は sending キーワードをつけることで、その値をActor境界外に渡した後に操作された場合、データ競合リスクを検知する
Before
- 以下コードにおいて、getNonSendableInvalid()はデータ競合のリスクがあるがコンパイラ側で検知できていない
@MainActor
struct S {
let ns: NonSendable
func getNonSendableInvalid() -> NonSendable {
// error: sending 'self.ns' may cause a data race
// note: main actor-isolated 'self.ns' is returned as a 'sending' result.
// Caller uses could race against main actor-isolated uses.
return ns
}
func getNonSendable() -> sending NonSendable {
return NonSendable() // okay
}
}sending付与によって、getNonSendableInvalid()エラーを出すようになる- ※
getNonSendable()はNonSendableが関数のActor境界に隔離されており、関数外で操作される恐れがないので、データ競合の恐れがない
@MainActor
struct S {
let ns: NonSendable
func getNonSendableInvalid() -> sending NonSendable {
// error: sending 'self.ns' may cause a data race
// note: main actor-isolated 'self.ns' is returned as a 'sending' result.
// Caller uses could race against main actor-isolated uses.
return ns
}
func getNonSendable() -> sending NonSendable {
return NonSendable() // okay
}
}いつ使うか
- Non-Sendableな値をSendable対応せずに、扱う側でActorを超えて扱いたい時
- 無理に@unchecked Sendableさせる必要がなくなる点でGood
技術メモ