Site icon image技術メモ

[SE-0434]Usability of global-actor-isolated types

参照


提案概要

  • 「Global actor isolated types(GlobalActorに隔離された型)」に関する、いくつかのConcurrencyルールの変更
    • GlobalActorに隔離された型とは?
      • @MainActorが付与された型などが一例
  • 提案内容は5つ
    • GlobalActorに隔離された型のストアドプロパティーの型が Sendable である場合に
      • nonisolated(unsafe) を付与せずとも、nonisoaltedな値として宣言することを許可する
      • その値が宣言されたmodule内部であれば、 nonisolated な値として扱う
    • GlobalActorに隔離された関数やクロージャを暗黙的に Sendable な関数やクロージャとして扱う
    • GlobalActorに隔離されたクロージャが、 Non-Sendable な値をキャプチャすることを許可する
    • Actor隔離されておらず、Sendableでもないclassのサブクラスが、GlobalActorに隔離されることを許可する。しかし、そのサブクラスはNon-Sendableでなければならない

GlobalActorに隔離された型のストアドプロパティーの型が Sendable である場合に nonisolated(unsafe) を付与せずとも、nonisoaltedな値として宣言することを許可する
&
GlobalActorに隔離された型のストアドプロパティーの型が Sendable である場合にその値が宣言されたmodule内部であれば、 nonisolated な値として扱う

概要

@MainActor
struct S {
  var x: Int = 0 // okay ('nonisolated' is inferred within the module)
}

extension S: Equatable {
  static nonisolated func ==(lhs: S, rhs: S) -> Bool {
    return lhs.x == rhs.x // okay
  }
}

なぜ必要か

  • 既存のSwiftバージョンでは、上記のxはデータ競合の恐れがないにも関わらず、nonisolated(unsafe)をつける必要があり、不要な手間である

GlobalActorに隔離された関数やクロージャを暗黙的に Sendable な関数やクロージャとして扱う

概要

func test(globallyIsolated: @escaping @MainActor () -> Void) {
  Task {
    // error: capture of 'globallyIsolated' with non-sendable type '@MainActor () -> Void' in a `@Sendable` closure
    await globallyIsolated()
  }
}

なぜ必要か

  • 上記 globallyIsolated closureは@MainActorで実行されるのでSendableなクロージャとして扱って差し支えないが、現行のSwiftバージョンではエラーになる

GlobalActorに隔離されたクロージャが、 Non-Sendable な値をキャプチャすることを許可する

概要

class NonSendable {}

func test() {
  let ns = NonSendable()

  let closure { @MainActor in
    print(ns) // 既存ではエラー
  }

  Task {
    await closure() // okay
  }
}

なぜ必要か

  • GlobalActorに隔離されたクロージャの利便性(Usability)を上げるため

どのように使うか

  • GlobalActorに隔離されたクロージャによってキャプチャされたインスタンスは、キャプチャ前のインスタンスとは別になることに注意
    • キャプチャした際に共通して起こる挙動なので特別な話ではない

備考

  • RegionBasedIsolationが適用されているので、nsが同一インスタンスではないことを実際に動かして検証できない

Actor隔離されておらず、Sendableでもないclassのサブクラスが、GlobalActorに隔離されることを許可する。しかし、そのサブクラスはNon-Sendableでなければならない

概要

Before

class NotSendable {}


@MainActor
class Subclass: NotSendable {} // error: main actor-isolated class 'Subclass' has different actor isolation from nonisolated superclass 'NotSendable'

After

  • サブクラスにGlobalActorの付与できるようにするが、GlobalActorに隔離された型を暗黙的にSendableとして扱うのはやめるよ
class NonSendable {
  func test() {}
}

@MainActor
class IsolatedSubclass: NonSendable {
  func trySendableCapture() {
    Task.detached {
      self.test() // error: Capture of 'self' with non-sendable type 'IsolatedSubclass' in a `@Sendable` closure
    }
  }
}

なぜ必要か

  • Concurrency周りの利便性を上げるため
    • サブクラスしたらGlobalActor付与でないのでは、基底クラス自体がGlobalActorに準拠している必要がでてくる

感想

  • 最後のは特にややこしいルール…

その他備考