0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Swift】throwするエラーの型を指定する方法

Posted at

はじめに

Swift6からTyped throwがサポートされ、throwするエラーの型を指定できるようになりました。そこで今回はTyped throwの基本的な使い方と、使うべき場面について調べたことをまとめます。

基本的な使い方

エラーを返す関数にthrows(エラーの型)のようにすることで、スローするエラーの型を指定することができます。

enum SampleError: Error {
    case invalidData
}

func errorFunction() throws(SampleError) {
    throw .invalidData
}

このerrorFunctionは常にSampleErrorをスローすることが保証されています。そのためdo-catchでキャッチできるエラーはSampleErrorになります。

do {
    try errorFunction()
} catch {
    print(error) // SampleError
}

SampleErrorをスローされるとは保障されていない関数とともに呼び出すと、キャッチされるエラーはany Errorになります。

enum SampleError: Error {
    case invalidData
}

func errorFunction() throws(SampleError) {
    throw .invalidData
}

func anyErrorFunction() throws {
    throw SampleError.invalidData
}

do {
    try errorFunction()
    try anyErrorFunction()
} catch {
    print(error) // any Error
}

ちなみに既存のthrowの書き方は、any Error型のTyped throwと同じです。

// この2つは同じ
func hoge() throws {...}
func hoge() throws(any Error) {...}

またthrowしない関数は、NeverのTyped throwと同じです。

func hoge() {...}
func hoge() throws(Never) {...}

Typed throwは変更に弱い

Typed throwは一見すると便利な機能ですが、限定的な場面以外では基本的に推奨されていません。Typed throwのプロポーザル1にも以下のように記載されています。

The error values themselves are always type-erased to any Error. This approach encourages errors to be handled generically, and remains a good default for most code.(エラー値自体は、常に任意のErrorに型消去されます。 このアプローチは、エラーを一般的に扱うことを推奨しており、ほとんどのコードにとって良いデフォルトであることに変わりはない。)

その理由はTyped throwは変更に弱いからです。

例えば以下のような関数があるとします。

func performTask() throws(MyError) { 
    throw MyError.networkFailure
}

のちに新しいエラー型をスローする必要に迫られた場合、関数のシグネチャを変更しなければなりません。シグネチャを変更するということは、この関数の呼び出し元すべてに影響を与えるということです。

逆にTyped throwを使わなかった場合、

func performTask() throws { 
    throw MyError.networkFailure
}

のちに新たなエラー型をスローすることになっても、関数内部のコードを変更するだけで済みます。

このような背景があり、ほとんどのケースではTyped throwを使うべきではありません。

Typed throwを使うべき場面

ではTyped throwを使うべき場面はというと、プロポーザルに記載されています。

  • エラーハンドリングが、モジュールやパッケージに閉じていること
  • rethrows や Result のようにそれ自体がエラーを生み出さず、ただ通過するだけの場合
  • オーバーヘッドが許容できない場合

1つ目はシンプルに、エラーがモジュール内で完結していて、外部に公開されていないパターンです。
3つ目は詳しくわかりませんが、容量的にTyped throwを使った方がいいケース?

個人的に2つ目の言っていることがわからなかったので、少し深掘りしてみました。

rethrowsをTyped throwに置き換える

rethrowには引数として受け取ったクロージャ以外のところでエラーをスローできないという制約があります。

func countMatching(_ numbers: [Int], predicate: (Int) throws -> Bool) rethrows -> Int {
    var count = 0
    var caughtError: Error?

    for number in numbers {
        do {
            if try predicate(number) {
                count += 1
            }
        } catch {
            caughtError = error
            break
        }
    }

    if let error = caughtError {
        throw error // ⭐️A function declared 'rethrows' may only throw if its parameter does
    }

    return count
}

このコードではクロージャとは別の場所でエラーをスローしているため、ビルドエラーが発生します。

そこでTyped throwとジェネリクスをうまく使えば、この問題を解決することができます。

func countMatching<E: Error>(_ numbers: [Int], predicate: (Int) throws(E) -> Bool) throws(E) -> Int {
    var count = 0
    var caughtError: E?

    for number in numbers {
        do {
            if try predicate(number) {
                count += 1
            }
        } catch {
            caughtError = error
            break
        }
    }

    if let error = caughtError {
        throw error
    }

    return count
}

このコードは問題なく動作します。

このようにその関数自体がエラーを発生させるのではなく、ただエラーを伝播させる場合はTyped throwが適しています。

ちなみにmapcount(where:)もTyped throwで実装されています。

public func map<T, E>(_ transform: (Element) throws(E) -> T) throws(E) -> [T] where E : Error
public func count<E>(where predicate: (Element) throws(E) -> Bool) throws(E) -> Int where E : Error

参考にした記事

  1. https://github.com/swiftlang/swift-evolution/blob/main/proposals/0413-typed-throws.md#introduction

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?