Skip to content

non-isolated

隔離されない状態や関数をnon-isolatedと呼びます。

func sailTheSea() {
}

このトップレベルの関数にはアクターによる隔離も、グローバルアクターによる隔離もありません。

型も同じです。以下の 純粋なclass は、 non-isolated です。

enum HungerLevel {
case hungry
case full
}
class Chicken {
let name: String
var currentHunger: HungerLevel
init(name: String, currentHunger: HungerLevel) {
self.name = name
self.currentHunger = currentHunger
}
}

sailTheSea() は、どの isolation domain にも属していません。そのため、actor の中からでも await なしで呼べます。

actor Ship {
func go() {
sailTheSea()
}
}

逆に、sailTheSea() の中から actor の状態へは触れません。course は Ship の isolation domain に隔離されているからです。

actor Ship {
var course = "北"
}
func sailTheSea(ship: Ship) {
ship.course = "南" // コンパイルエラー
}

境界を越えて状態を読み書きする場合には、await を使います。

extension Ship {
func go() {
course = "南"
}
}
func sailTheSea(ship: Ship) async {
await ship.go()
}

Chicken も non-isolated です。currentHunger を持っていて、それが var であり可変状態を持っていますが、このクラスの定義はコンパイルできます。

actor が二つあります。どちらも、Chicken を受け取る関数を持っています。この二つの定義も、コンパイルできます。

actor Barn {
func look(at chicken: Chicken) {
print(chicken.currentHunger)
}
}
actor Coop {
func feed(_ chicken: Chicken) {
chicken.currentHunger = .full
}
}

Barn.look は currentHunger を読みます。Coop.feed は .full に書き換えます。

しかし、次のように同時にnon-isolatedな可変状態を読み書きしようとした時にはどうなるでしょう。

func morning(barn: Barn, coop: Coop) async {
let chicken = Chicken(name: "まる", currentHunger: .hungry)
await barn.look(at: chicken) // コンパイルエラー
await coop.feed(chicken)
}

morning は non-isolated です。await で順番に書いてあっても、Barn と Coop は別々の isolation domain なので、同じ currentHunger を同時に触れることがあります。swiftはコンパイラによりこのデータ競合のリスクを検知してコンパイルエラーにします。

actor や global actor に隔離された型の中でも、メソッドに nonisolated を付けると、そのメソッドは non-isolated になります。その書き方は、actor のページで見ます。