Result の async サポート
Async Result Support
このダイジェストはClaude Opus 4.7 / 4.8によって生成されたものです(License)。原文はこちら↗。
01 何が問題だったのか
標準ライブラリの Result 型は、エラーを throw する可能性のある処理の結果(成功値もしくは失敗の理由)を値として扱うための型で、特に throwing なコードを別の文脈に持ち越したいときに便利です。中でも Result.init(catching:) は、クロージャを実行してその戻り値を .success、throw されたエラーを .failure に詰める便利なイニシャライザで、throwing 関数の結果を Result 値に変換する用途で広く使われてきました。
let result = Result {
try syncWork()
}
しかし、このイニシャライザは同期クロージャしか受け取れず、非同期コードに対応する版は存在しません。async な処理の結果をそのまま Result に詰めたいケースでは、自前で同等のイニシャライザを書き起こすほかなく、各コードベースで似たようなコードが繰り返し書かれてしまっていました。これは Result 型の利便性を考えると不釣り合いに不便な状況であり、標準ライブラリ側で async 版を提供することが望まれていました。
02 どのように解決されるのか
Result に、async クロージャを受け取る init(catching:) のオーバーロードを追加します。これにより、async な throwing コードの結果をそのまま Result 値として組み立てられるようになります。
let result = await Result {
try await asyncWork()
}
追加されるイニシャライザは次のとおりです。
extension Result where Success: ~Copyable {
@_alwaysEmitIntoClient
public nonisolated(nonsending) init(
catching body: nonisolated(nonsending) () async throws(Failure) -> Success
) async {
do {
self = .success(try await body())
} catch {
self = .failure(error)
}
}
}
設計上のポイントは次の点です。
Success: ~Copyableの制約により、non-copyable な成功値も扱えます。throws(Failure)で型付きスロー(typed throws)に対応しており、FailureがErrorでなくとも適切な失敗型として捕捉できます。- クロージャと初期化処理の双方が
nonisolated(nonsending)で宣言されているため、呼び出し元のアクター上で実行されます。@MainActor上で呼び出せば、クロージャ本体も@MainActor上で動作します。 @_alwaysEmitIntoClient属性が付与されており、古い OS バージョンへもバックデプロイできます。
利用側のコードは、これまで自前で書いていた Result.init(catching:) の async 版を、標準ライブラリ提供のものに置き換えるだけで済みます。