【Swift 5.5】詳解 Result
この記事は DeNA 21新卒×22新卒内定者 Advent Calendar 2021 #DeNA21x22AdCal の9日目の記事です。
Result
とは Swift 5.0 で導入された、成功または失敗のいずれかを表すことのできる型で、成功失敗それぞれの場合における値を associated values として持たせることもできます。
この記事では Result
が持つイニシャライザ、メソッド、プロパティ、演算子について説明します。
この記事で取り扱う Swift のバージョンは 5.5 です。
Result
の定義
Result
はここで定義されており、実態は enum となっています。
@frozen enum Result<Success, Failure> where Failure : Error
成功・失敗のそれぞれの場合に伝えたい値を入れるための Success
, Failure
と2つのジェネリクスが用意されています。ただし Failure
は Error
に準拠している必要があります。
たとえば、成功した場合は String
、失敗した場合は MyError
を伝えたいときはこのようにします。
enum MyError: Error {
case invalid
}
let result: Result<String, MyError>
また、Result
は Equatable
、 Hashable
、Sendable
に準拠しています(ただし Success
と Failure
がどちらもそれらに準拠していることが条件)。
Result が準拠しているプロトコル
extension Result: Equatable where Success: Equatable, Failure: Equatable { }
extension Result: Hashable where Success: Hashable, Failure: Hashable { }
extension Result: Sendable where Success: Sendable, Failure: Sendable { }
Result
の基本
Result
は2つの case を持ちます。
.success(_:)
.success(_:)
はその Result
が成功していることを示したいときに使います。成功した際に伝えたい値を associated value として持ちます。
let result: Result<String, MyError> = .success("成功!")
print(result) // success("成功!")
.failure(_:)
.failure(_:)
はその Result
が失敗していることを示したいときに使います。失敗した際に伝えたい Error
を associated value として持ちます。
enum MyError: Error {
case invalid
}
let result: Result<String, MyError> = .failure(.invalid)
print(result) // failure(MyError.invalid)
基本の使い方
Result
は enum なので、たとえば switch 文を使って簡単に成功・失敗した際の続きの処理を書くことができます。
たとえば do-catch の場合は catch で得られるエラーが Error
であるということしか分かりません。しかし Result
は Failure
に自分で指定した MyError
が入ることが確定しています。
let result: Result<String, MyError> = // ...
switch result {
case .success(let str):
print("成功した:", str)
case .failure(let myError):
print("失敗した")
switch myError {
case .invalid:
print("invalid")
}
}
Result
に変換する
throws なメソッド等を Result
のイニシャライザに init(catching:)
があります。これを使うことで do-catch で囲む必要のある throws なメソッド等を Result
に変換できます。
func doSomething() throws -> String {
return "成功"
// または throw MyError.invalid など
}
let result: Result<String, Error> = .init(catching: doSomething)
print(result) // success("成功")
// async throws なメソッド等でも使えるように自分で拡張する一例
extension Result where Failure == Swift.Error {
init(catching body: () async throws -> Success) async {
do {
self = .success(try await body())
} catch {
self = .failure(error)
}
}
}
func doSomething() async throws -> String {
return "成功"
// または throw MyError.invalid など
}
let result: Result<String, Error> = await .init(catching: doSomething)
print(result) // success("成功")
Result
を throws なメソッドに変換する
また逆に Result
の get()
を使うことによって、Result
を do-catch で囲む必要のある throws なメソッドに変換することもできます。
let result: Result<String, Error> = .success("成功!")
do {
let str = try result.get()
print(str) // 成功!
} catch {
print(error)
}
func doSomething() async -> Result<String, MyError> {
return .success("成功!")
// または return .failure(.invalid) など
}
do {
let result = try await doSomething().get()
print(result)
} catch {
print(error)
}
map(_:)
と flatMap(_:)
Result
を別な Result
に変換する
map(_:)
、mapError(_:)
が用意されており、すでに得た Result
を別な Result
に変換できます。
map(_:)
let intResult: Result<Int, Error> = .success(20211225)
print(type(of: intResult)) // Result<Int, Error>
print(intResult) // success(20211225)
let stringResult = intResult.map { String($0) }
print(type(of: stringResult)) // Result<String, Error>
print(stringResult) // success("20211225")
mapError(_:)
import Foundation
struct MyError1: Error {
}
struct MyError2: Error {
let error: Error
let uuid: UUID
init(_ error: Error) {
self.error = error
self.uuid = UUID()
}
}
let result1: Result<Int, MyError1> = .failure(MyError1())
print(type(of: result1)) // Result<Int, MyError1>
print(result1) // failure(MyError1())
let result2 = result1.mapError { MyError2($0) }
print(type(of: result2)) // Result<Int, MyError2>
print(result2) // failure(MyError2(error: MyError1(), uuid: 423CF494-F5DE-42DC-B8E7-0198BFA041F7))
Result
に変換するときにネストを回避する
別な flatMap(_:)
、flatMapError(_:)
が用意されています。map(_:)
や mapError(_:)
のように、すでに得た Result
を別な Result
に変換できますが、Result
がネストされることがなくなります。
下記の result2
と result3
を見比べてみましょう。
flatMap(_:)
let result1: Result<Int, Error> = .success(20211225)
print(type(of: result1)) // Result<Int, Error>
print(result1) // success(20211225)
let result2 = result1.map {
Result<Int, Error>.success($0 + 10000)
}
print(type(of: result2)) // Result<Result<Int, Error>, Error>
print(result2) // success(Result<Int, Error>.success(20221225))
let result3 = result1.flatMap {
Result<Int, Error>.success($0 + 10000)
}
print(type(of: result3)) // Result<Int, Error>
print(result3) // success(20221225)
flatMapError(_:)
import Foundation
struct MyError1: Error {
}
struct MyError2: Error {
let error: Error
let uuid: UUID
init(_ error: Error) {
self.error = error
self.uuid = UUID()
}
}
let result1: Result<Int, MyError1> = .failure(MyError1())
print(type(of: result1)) // Result<Int, MyError1>
print(result1) // failure(MyError1())
let result2 = result1.mapError { MyError2($0) }
print(type(of: result2)) // Result<Int, MyError2>
print(result2) // failure(MyError2(error: MyError1(), uuid: B1DCC40D-907F-4075-A7C0-B4A1B47A1005))
let result3 = result1.flatMapError {
Result<Int, MyError2>.failure(MyError2($0))
}
print(type(of: result3)) // Result<Int, MyError2>
print(result3) // failure(MyError2(error: MyError1(), uuid: E98BDFDB-50BF-46F9-98A5-52343F52BD72))
Publisher
を使う
Combine の Result
には publisher
もあり、 sink(receiveValue:)
や sink(receiveCompletion:receiveValue:)
などを使うことができます。
let result: Result<Int, Error> = .success(20211225)
result.publisher
.sink { completion in
print(type(of: completion))
print("result receiveCompletion:", completion)
} receiveValue: { value in
print(type(of: value))
print("result receiveValue:", value)
}
// Int
// result receiveValue: 20211225
// Completion<Error>
// result receiveCompletion: finished
もし Result
が .failure(_:)
となった場合、Subscribers.Completion
が .failure(_:)
となります。エラーの内容は Result.failure(_:)
から Subscribers.Completion.failure(_:)
に渡っています。
enum MyError: Error {
case invalid
}
let result: Result<Int, MyError> = .failure(.invalid)
result.publisher
.sink { completion in
print("result receiveCompletion:", completion)
} receiveValue: { value in
print("result receiveValue:", value)
}
// Completion<MyError>
// result receiveCompletion: failure(MyError.invalid)
お知らせ
この記事は DeNA 21新卒×22新卒内定者 Advent Calendar 2021 #DeNA21x22AdCal の9日目の記事でした。他の記事もぜひどうぞ!
参考
Discussion