Skip to content

Instantly share code, notes, and snippets.

@stzn
Last active June 15, 2024 01:03
Show Gist options
  • Save stzn/d10fe8bcf2ee94a9b7b6ec604f1d9bc5 to your computer and use it in GitHub Desktop.
Save stzn/d10fe8bcf2ee94a9b7b6ec604f1d9bc5 to your computer and use it in GitHub Desktop.
Swift6.0 CHANGELOG

原文: https://github.com/apple/swift/blob/main/CHANGELOG.md#swift-60

ここまで反映: https://github.com/apple/swift/commit/0eaa985998dc78e916fbc7285628dd7d86d9f1ae

  • Swift 6は、コンパイル時にデータ競合のリスクを防止するための新しい言語モードを提供します。これは、データの隔離(isolation)を行うことで保証されています。コンパイラは、同時に実行されるコード間の境界を越えて渡されるデータが、安全に同時に参照可能であるか、または値への相互排他的アクセスが強制されているかを検証します。

    Swift 5.10以降、データ競合の安全性チェックは -strict-concurrency=complete コンパイラフラグを通じて利用可能でした。Swift 5.10のComplete Concurrencyチェックは、過度に制限的であり、Swift 6では、より良いSendableの推論による偽陽性(false positive)のデータ競合に対するワーニングの除去、non-Sendable型の値を隔離(isolation)の境界を越えて渡す際の相互排他的なアクセスを証明する新しい分析等により、多くの偽陽性のデータ競合ワーニングが削除されます。

    -swift-version 6 コンパイラフラグを使用して、Swift 6 言語モードを有効にできます。

  • SE-0428: 分散(Distributed)アクターは、新しい@Resolvableマクロとランタイムの変更により、完全なサーバー/クライアント分割システムをサポートできるようになりました。

    これにより、クライアントとサーバーアプリケーション間で「APIモジュール」を共有し、期待されるAPIコントラクトを使って、解決可能な分散アクタープロトコルを宣言し、サーバーがそれらのアクターを実装している具体的なタイプを知らなくても、そのコントラクトを使って呼び出すことが可能になりました。

    このようなプロトコルの宣言は、次のようになります。

import Distributed 

@Resolvable
protocol Greeter where ActorSystem: DistributedActorSystem<any Codable> {
  distributed func greet(name: String) -> String
}

そして、そのようなアプリケーションをサポートするためのモジュール構造は、次のようになります。

                         ┌────────────────────────────────────────┐
                                         APIモジュール              
                         │========================================│
                          @Resolvable                            
                          protocol Greeter: DistributedActor {   
                 ┌───────┤   distributed func greet(name: String) ├───────┐
                         }                                             
                        └────────────────────────────────────────┘       
                                                                         
                                                                            
┌────────────────────────────────────────────────┐      ┌──────────────────────────────────────────────┐
             クライアントモジュール                                         サーバモジュール                   
│================================================│      │==============================================│
 let g = try $Greeter.resolve(...) /*new*/             distributed actor EnglishGreeter: Greeter {  
 try await greeter.hello(name: ...)                      distributed func greet(name: String) {     
└────────────────────────────────────────────────┘           "Greeting in english, for \(name)!"      
/* クライアントはEnglishGreeterの型を知る必要はない */              }                                                
                                                         }                                            
                                                        └──────────────────────────────────────────────┘
  • SE-0424: シリアルエグゼキュータは、最終手段を提供するために、カスタムエグゼキュータを実装することによって実装される新しいカスタマイゼーションポイントcheckIsolation()を追加します。

    これにより、Dispatchモジュールは、より高度な隔離(isolation)チェックを実装できるようになり、さらに「別の特定のキューをターゲットとしているキューの上に」存在するアクターも、これらのAPIを使用して正しく検出できます。

  • クロージャをパック展開式(pack expansion expression)の中に記述できるようになりました。これにより、各クロージャは、他のパラメーターパック(parameter pack)の対応する要素をキャプチャするクロージャのパラメーターパックを作成できます。 たとえば

struct Manager<each T> {
  let fn: (repeat () -> (each T))

  init(_ t: repeat each T) {
    fn = (repeat { each t })
  }
}
  • SE-0431: クライアントが直接読めるような形で、アクターの隔離を動的に実行するための関数の値を要求できるようになります。

func apply<R>(count: Int,
              operation: @isolated(any) async () -> R) async -> [R]
    where R: Sendable {
  // implementation
}

隔離は、(any Actor)?型の.isolationプロパティから読み取れます。

let iso = operation.isolation

この機能は、標準ライブラリのTask生成APIに導入されています。その結果、アクターによって隔離された関数でTaskを作成すると、Taskがアクター上で同期的にエンキューされます。これは、@MainActorと同様にジョブがエンキューされた順序で実行されることを保証するため、推移的なイベントの順序を保証するために使用できます。関数が明示的に隔離されていない場合でも、Swiftは、決められた隔離領域ではない異なる隔離領域を使って作業することで、実際に実行される関数のエンキュー方法を最適化できます。

  • SE-0423: @preconcurrency属性を使用すると、プロトコル実装(witness)が隔離(isolated)している場合、同期的なnonisolatedのプロトコル要件のプロトコル実装に対する静的なアクター隔離チェックを、動的なチェックに置き換えることができます。これは、SwiftプログラムがC/C++/Objective-Cで記述されたフレームワークと連携する必要があり、その実装が静的なデータ競合の安全性チェックができない場合によく使用されます。
public protocol ViewDelegateProtocol {
  func respondToUIEvent()
}

@preconcurrencyをプロトコルに準拠した宣言に付けることで、@MainActorに隔離された型がViewDelegateProtocolに準拠できるようになりました。

@MainActor
class MyViewController: @preconcurrency ViewDelegateProtocol {
  func respondToUIEvent() {
    // implementation...
  }
}

コンパイラは、常に@MainActorの隔離コンテキストで実行されることを確認するために、respondToUIEvent()のプロトコル実装に動的チェックを行います。

さらに、コンパイラは、次の動的アクター隔離チェックを行います。

  • クラスの同期アクター隔離メンバの@objcのthunk
  • アクター隔離を消去し、厳格な並行性チェックをまだ採用していないAPIに渡される同期的なアクター隔離関数の値
  • Swift 6 ライブラリからインポートされた同期アクター隔離関数の呼び出し側

動的アクター隔離チェックは、-disable-dynamic-actor-isolationフラグを使用して無効にできます。

  • SE-0420: async関数は、デフォルト値を#isolationとするisolatedパラメータを宣言することで、呼び出し元の隔離を明示的に継承できるようになりました。
func poll(isolation: isolated (any Actor)? = #isolation) async -> [Item] {
  // implementation
}

呼び出し元がアクターに隔離されている場合、(もし渡せないとしたら問題になるような)隔離された状態を関数に渡すことができます。たとえば、中断(suspend)することなく、ファストパス(fast path)で素早くreturnできる場合など、望ましくないスケジューリング変更を排除できる可能性があります。

  • SE-0418:

    コンパイラは、non-Sendableの値をキャプチャできない関数やKeyPathリテラル式に対して、自動でSendableを適用します。

次の型を使用して、新しい推論規則を説明しましょう。

public struct User {
  var name: String

  func getAge() -> Int { ... }
}

Key Pathの\User.nameは、non-Sendableな値をキャプチャしないため、WritableKeyPath<User, String> & Sendableとして推論されます。

同じことが、関数としてのKey Path変換にも当てはまります。

let _: @Sendable (User) -> String = \User.name // Ok

UserSendableのstructであるため、getAgeの未適用の参照から生成される関数値は、@Sendableとしてマークされます。

let _ = User.getAge // Inferred as `@Sendable (User) -> @Sendable () -> Int`

let user = User(...)
user.getAge // `@Sendable () -> Int`と推論される
  • SE-0432: コピー不可能(Noncopyable)の列挙型は、switchの対象となる値を消費(consume)せず、switchを使用してパターンマッチングできますが、。
enum Lunch: ~Copyable {
  case soup
  case salad
  case sandwich
}

func isSoup(_ lunch: borrowing Lunch) -> Bool {
  switch lunch {
    case .soup: true
    default: false
  }
}
  • SE-0429: 特定の型のコピーできないフィールドを個別に消費(consume)できるようになりました。
struct Token: ~Copyable {}

struct Authentication: ~Copyable {
  let id: Token
  let name: String

  mutating func exchange(_ new: consuming Token) -> Token {
    let old = self.id  // <- `self`の一部を消費(consume)している
    self = .init(id: new, name: self.name)
    return old
  }
}
  • SE-0430:

    リージョンベース隔離は、明示的なsendingアノテーションを関数のパラメータおよび戻り値に適用できるように拡張されました。sendingの付いた関数のパラメータまたは戻り値は、関数の境界で分断される必要があり、それによって、関数の本文(body)または関数の呼び出し元それぞれで、隔離領域をまたがっても安全に送信される、またはアクター隔離された領域に統合される能力を手に入れます。たとえば
func parameterWithoutSending(_ x: NonSendableType) async {
  // Error! あるTaskの領域に隔離されている値をMainActorに送れない!
  await transferToMainActor(x)
}

func parameterWithSending(_ x: sending NonSendableType) async {
  // `x`は`sending`なので、分断されているからOK。
  await transferToMainActor(x)
}
  • SE-0414:

    コンパイラは、Sendableプロトコルに準拠しない値が安全に隔離境界を超えて送信できるかどうかを判断できるようになりました。これは、相互に影響を及ぼす可能性のある2つの値の関連性を、コンパイラが慎重に推論するための隔離領域の概念を導入することで行われます。隔離領域の使用により、値の送信後に、呼び出し元でその値(およびそれを参照する可能性がある他の値)が使用されないため、コンパイラは、Sendableプロトコルに準拠しない値を、隔離境界を超えて送信して競合状態を引き起こすことはないことを保証できます。
actor MyActor {
    init(_ x: NonSendableType) { ... }
}

func useValue() {
  let x = NonSendableType()
  let a = await MyActor(x) // Error without Region Based Isolation!
}
  • SE-0427: プロトコル、ジェネリックパラメータ、および存在型(Existential)において、Copyableを抑制できます。
// Copyableへの準拠を必須としない
protocol Flower: ~Copyable {
  func bloom()
}

// Noncopyable type
struct Marigold: Flower, ~Copyable {
  func bloom() { print("Marigold blooming!") }
}

// Copyable type
struct Hibiscus: Flower {
  func bloom() { print("Hibiscus blooming!") }
}

func startSeason(_ flower: borrowing some Flower & ~Copyable) {
  flower.bloom()
}

startSeason(Marigold())
startSeason(Hibiscus())

ジェネリック型に ~Copyableと書くことにより、その型に表示されるはずだったデフォルトのCopyable制約を抑制します。これにより、Copyableに準拠しないコピー可能な型も、そのようなプロトコルに準拠してジェネリック型として置き換えることができます。この機能を完全に使えるようにするには、新しいSwift 6ランタイムが必要です。

  • Swift 5.1の導入以来、@TaskLocalプロパティラッパは、タスク固有の値のバインディングの作成とアクセスに使用されてきました。プロパティラッパは、今では安全に同時使用できない可能性があるとして警告されるようになっている、可変のストレージを導入します。

Swift 6言語モードでは、タスクローカルをポテンシャルにスレッドセーフではないものとマークしないようにするために、タスクローカルはマクロを使用して実装されています。マクロは、他と同じく、一般的なセマンティクスと使用パターンを持っていますが、Swift 6のタスクローカルでは扱えない2つの破壊的変更が起きる状況があります。

それは、タイプエイリアスとの組み合わせで、タスクローカルの初期化に暗黙のデフォルトnil値を使用するときです。

// Swift 5.xではOK、 Swift 6.xではNG

typealias MyValue = Optional<Int> 

@TaskLocal
static var number: MyValue // Swift 6: エラー。デフォルト値を明示的に指定してください。

// Solution 1: デフォルト値を指定する
@TaskLocal
static var number: MyValue = nil

// Solution 2: タイプエイリアスを避ける
@TaskLocal
static var number: Optional<Int>

一方で、タスクローカル変数は、これまで不可能だったグローバルプロパティとして宣言できるようになりました。

  • Swift 5.10は、SE-0309で導入されたセマンティックチェックを見逃していました。型のコンテキストでは、関連型(associated type)やSelf要件を持つプロトコルPへの参照には、anyキーワードを使用する必要がありますが、これはネストしたジェネリック引数では強制されていませんでした。この提案により、これは現在エラーになってしまいます。
protocol P { associatedtype A }
struct Outer<T> { struct Inner<U> { } }
let x = Outer<P>.Inner<P>()  // エラー

この誤りを修正するには、適切な場所にanyを追加してください。たとえば、Outer<any P>.Inner<any P>とします。

  • Swift 5.10はSE-0346によって、特定の無効なOpaque型の戻り値を受け入れるようになりました。制約のあるOpaque型の戻り値のジェネリック引数が、Primary associated typeの要件を満たさない場合、ジェネリック引数は何も警告なしに無視され、型チェックは何もなかったかのように通過します。これは現在、エラーが出力されます。
protocol P<A> { associatedtype A: Sequence }
struct G<A: Sequence>: P {}

func f() -> some P<Int> { return G<Array<Int>>() }  // error

上記の戻り値の型は、returnステートメントに一致するようにsome P<Array<Int>>と書かれるべきです。また、こういった状況において、エラーとなる制約を削除し、より抽象的な上限であるsome Pを使用することで、過去の壊れた振る舞いを復元できます。

  • : for-inループ文は、パック展開式(pack expansion expression)を受け入れられるようになりました。これにより、対応する値パックの要素を反復処理できます。この形式は、パターンマッチング、制御転送文、およびSequence駆動(Sequenceに準拠することで使える)のfor-inループで利用可能なその他の機能をサポートしています(where節は除く)。以下は、パックの反復処理を使用した任意の長さのタプルに対する等値演算子の実装例です。
func == <each Element: Equatable>(lhs: (repeat each Element),
                                  rhs: (repeat each Element)) -> Bool {

  for (left, right) in repeat (each lhs, each rhs) {
    guard left == right else { return false }
  }
  return true
}

パック展開式に対応する値パックの要素は、必要なときに評価されます。つまり、i番目の要素はi番目の反復で評価されるということです。

func doSomething(_: some Any) {}

func evaluateFirst<each T>(_ t: repeat each T) {
  for _ in repeat doSomething(each t) {
    break
  }
}

evaluateFirst(1, 2, 3) 
// 'doSomething'はパックの最初の要素に対してだけ呼ばれる
  • SE-0352: Swift 6の言語モードは、一般的な関数に渡される「自己準拠(self-conformance)型」(エラーや@objcプロトコルなど)を持つ存在型(Existentail)の値を開きます(具体的な型に置き換える)。たとえば、
func takeError<E: Error>(_ error: E) { }

func passError(error: any Error) {
  takeError(error)  // Swift 5では`any Error`を開かない, Swift 6では開く
}

この動作は、今upcoming featureフラグのImplicitOpenExistentialsを使用して、Swift 6 言語モードの前に有効にできます。

  • SE-0422: ビルトインではないExpressionマクロも、各呼び出し場所で展開されるデフォルト引数として使用できるようになりました。たとえば、Library.swiftでデフォルト引数として使用されているカスタムの#CurrentFileマクロは、「Library.swift」とはもう展開されません。
@freestanding(expression)
public macro CurrentFile() -> String = ...

public func currentFile(name: String = #CurrentFile) { name }

代わりに、関数が呼び出された場所で展開されます。

print(currentFile())
// "main.swift"と出力

展開されたコードは、呼び出し元で宣言された要素を使用できます。

var person = "client"
greetPerson(/* greeting: #informalGreeting */)
// もしマクロが"Hi \(person)"を展開するとしたら、"Hi client"と出力される
  • : タスクは、タスクのエグゼキュータの設定(Preference)を反映できるようになりました。これにより、(カスタムエグゼキュータを宣言していない)デフォルトのアクターで実行されるタスクや、nonsiolatedの非同期関数は、常にデフォルトのグローバルConcurrentプールで実行されるのではなく、優先するべきエグゼキュータにフォールバックできます。

タスクのエグゼキュータの設定は、withTaskExecutorPreference関数を使用して指定できます。

nonisolated func doSomething() async { ... }

await withTaskExecutorPreference(preferredExecutor) {
  doSomething()

あるいは、新しいUnstructuredタスクやChildタスク(例: タスクグループ)を作成する場合です。

Task(executorPreference: preferredExecutor) {
  // 'preferredExecutor'常で実行される
  await doSomething() // `doSomething`の本文は'preferredExecutor'で実行される
}

関数は、関数シグネチャの一部としてthrowするエラーの型を指定できるようになりました。たとえば、

func parseRecord(from string: String) throws(ParseError) -> Record { ... }

parseRecord(from:)の呼び出しは、Recordインスタンスを返すか、ParseError型のエラーをthrowします。例えば、do..catchブロックは、エラー変数がParseError型であると推論します。

do {
  let record = try parseRecord(from: myString)
} catch {
  // エラーの型は`ParseError`
}

型付きthrowsはthrowing関数とnon-throwing関数を一般化したものです。throwsと指定された関数は(エラーの種類を明示的に指定しない)throws(any Error)と等価であり、throwsでない関数はthrows(Never)と等価です。throws(Never)である関数の呼び出しはエラーをthrowしません。

型付きthrowは、rethrowsよりも正確な方法でパラメータからエラー型を伝播するためにジェネリック関数で使用できます。たとえば、Sequence.map演算はクロージャパラメータからthrowされたエラー型を伝播させることができ、クロージャと同じ型のエラーのみをスローすることを示します。

extension Sequence {
  func map<T, E>(_ body: (Element) throws(E) -> T) throws(E) -> [T] { ... }
}

エラーをthrowしないクロージャがパラメータに渡された場合、mapはthrowしません。

SE-0110の実装により、Swift 4では、特定の曖昧さのない型構文について、パラメータ名ではなくパラメータの型のみで構成されるクロージャパラメータ構文が誤って正当なものと見なされるようになってしまいました。たとえば、

let closure = { ([Int]) in }

これは、Swift 5.2以上からは警告が表示されており、この構文はSwift 6ではエラーになります。

  • #71075:

    _SwiftConcurrencyShimsは、利用できないかもしれないにも関わらず、exit関数を宣言するために使用されていました。この宣言は削除され、適切なCライブラリモジュールからインポートする必要があります。(例: DarwinやSwiftGlibc)

  • SE-0270:

    標準ライブラリは、非連続な(noncontiguous)要素に対してコレクション操作を実行するためのAPIを提供します。

var numbers = Array(1...15)

// すべての偶数のインデックスを見つける
let indicesOfEvens = numbers.indices(where: { $0.isMultiple(of: 2) })

// 偶数にだけ操作を実行する
let sumOfEvens = numbers[indicesOfEvens].reduce(0, +)
// sumOfEvens == 56

// 開始地点から偶数を集めることができる
let rangeOfEvens = numbers.moveSubranges(indicesOfEvens, to: numbers.startIndex)
// numbers == [2, 4, 6, 8, 10, 12, 14, 1, 3, 5, 7, 9, 11, 13, 15]
// numbers[rangeOfEvens] == [2, 4, 6, 8, 10, 12, 14]

標準ライブラリーは、新しいindices(where:)関数を提供し、非連続なインデックスの集合を表す新しい型であるRangeSetを作成します。RangeSetはそのインデックスの型に対して汎用的であり、コレクションから要素を収集、移動、削除するような、非連続なインデックスに対する操作を実行するために使用できます。さらに、RangeSetは、あらゆるComparableに準拠したコレクションのインデックスに対して汎用的であり、リスト内の項目の選択、またはフィルターや検索結果の絞り込みを行うために使用できます。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment