Site icon image技術メモ

[SE-0430]sending parameter and result values

参照


提案概要

  • 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

その他備考