fatalError(), preconditionFailure(), assertionFailure()...を掘り下げる🤔

# 🤔そういやfatalErrorってどういう関数なんや？

こんにちは、`🤔わからんこと掘り下げ太郎`です。<br>
まさかよくわからないまま`print()`みたいなノリで、`fatalError()`を意図しない動作が起こりうる場所で使っているやつはいねえよなぁ？<br>

`⌘cmd` + `^ctl` ＋ click
で`fatalError()`の定義に飛んでみると
Swift > Assert ってファイルの中に記述されている。<br>

```Swift > Assert.swift
public func assert(...)                         //1
public func precondition(...)                   //2
@inlinable public func assertionFailure(...)    //3
public func preconditionFailure(...) -> Never   //4
public func fatalError(...) -> Never            //5
```

ちょうど`precondition()`とかもよくわかっていなかったし、順番に全部見ていく。<br>
## 1. assert()
```swift
public func assert(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line)
```
- `assert()`はデバッグ中に条件をチェックするために使用される関数。<br>
- デバッグ中のみ有効でリリースビルドでは無効化される。<br>
- conditionがfalseの時、プログラムの実行を停止しmessage、file、lineをprintする。<br>

<details><summary>🤔@autoclosureって何？</summary><div>


`@autoclosure`は引数を自動的にクロージャに変換してくれる機能。
`@autoclosure`の嬉しい点：<br>
- 簡潔明瞭：クロージャの記述を省略して関数の引数として直接式を渡せる。
- 遅延評価：関数呼び出し時ではなく、必要な時点でそのクロージャを実行できる。
- 条件付きで実行:遅延評価されるので、不必要な場合は実行されずサボれる。
</div></details>


```swift
file: StaticString = #file, line: UInt = #line
```
を詳しくみる。<br>

`#file`, `#line`で`assert()`が呼ばれたfile, lineを取得できるらしい。
`#hoge`はプリプロセッサ指令といい「文字の集合であるソースコード上で機能する命令」だそうだ。<br>


https://qiita.com/Rudiments/items/768c8926640919ffdd31

ということは以下のようなConditional Completion Blockもプリプロセッサ指令なのかな？
```swif
#if os(iOS)
...
#else
...
#endif
```

あと`file`の型の`StaticString`ってなんだ？<br>
`⌘cmd` + `^ctl` ＋ click
で定義に飛ぶと
```Swift > String > StaticString.swift
@frozen public struct StaticString : Sendable {
...
}
```
流石にこれ以上掘り下げるのは面倒くさいのでChatGPT先生に聞いてみた。<br>
（間違いがある可能性あり）

<details><summary>🤔StaticStringって何？</summary><div>

ChatGPT:<br>
`StaticString`は、Swiftの組み込み型であり、不変の文字列データを表します。`StaticString`は文字列リテラルのように扱われますが、不変であり、メモリ上に固定された形式で格納されます。このため、実行時の文字列操作やメモリ管理のコストが最小限に抑えられます。

`StaticString`は、主にフォーマット文字列として使用され、`print()`や`assert()`などの関数でフォーマット指定子と組み合わせて利用されます。フォーマット指定子は`%`を使って`StaticString`内のプレースホルダーと対応します。

以下は、`StaticString`の使用例です：

```swift
let name = "John"
let age = 30
let message: StaticString = "\(name) is \(age) years old."

print(message) // John is 30 years old.
```

上記の例では、`message`は`StaticString`型として宣言されており、`name`と`age`の値をフォーマットしています。

`StaticString`は不変であるため、文字列の値を直接変更することはできません。また、`StaticString`は文字列操作の機能が限定されているため、一般的な文字列操作メソッドやプロパティ（例：`count`や`isEmpty`）を使用することはできません。

`StaticString`は主にパフォーマンスやメモリ使用量の最適化が必要な場合に使用されます。ただし、一般的な文字列操作や変更が必要な場合は、`String`型を使用する方が適しています。

</div></details>

<details><summary>🤔@frozenって何？</summary><div>

ChatGPT:<br>
`@frozen`は、Swiftの属性（Attribute）の一種であり、データ構造体（struct）や列挙型（enum）が「凍結された」（変更不可）であることを示すために使用されます。

`@frozen`属性をデータ構造体や列挙型に適用することで、その型の宣言がコンパイラによって最適化され、特定の条件下でより効率的に使用されることが保証されます。

主な利点としては以下のようなものがあります：

1. 最適化: `@frozen`属性を使用することで、コンパイラはデータ構造体や列挙型が変更不可であることを知るため、特定のコードパスでの最適化が可能になります。例えば、列挙型の場合、特定の値に対するスイッチ文の最適化や、列挙型のサイズの最小化が行われることがあります。

2. パフォーマンス向上: `@frozen`属性を使用することで、メモリのレイアウトやアクセス方法などが最適化され、実行時のパフォーマンスが向上する可能性があります。

3. APIの安定性: `@frozen`属性を使用することで、特定のデータ型を変更不可と宣言することができます。これにより、他のモジュールやクライアントコードに対して、そのデータ型が安定していることが伝わります。

以下は、`@frozen`属性の使用例です：

```swift
@frozen enum Status {
    case success
    case failure
}

@frozen struct Point {
    var x: Int
    var y: Int
}
```

上記の例では、`Status`列挙型と`Point`構造体に`@frozen`属性が適用されています。これにより、コンパイラはそれらの型が変更不可であることを認識し、最適化を行うことができます。

なお、`@frozen`属性はリリースビルド時にのみ有効であり、デバッグビルド時には効果を発揮しません。また、`@frozen`属性を使用する場合は、変更不可であることを十分に検討し、将来的な拡張性や互換性についても考慮する必要があります。
</div></details>

<details><summary>🤔Sendableって何？</summary><div>

ChatGPT:<br>
`Sendable`は、Swift 5.5で導入されたプロトコル（Protocol）であり、非同期処理や並行性に関連する機能をサポートするために使用されます。

`Sendable`プロトコルを準拠する型は、非同期コンテキスト（`Task`など）を介して安全に送信（送出）およびアクセスできることを示します。これにより、並行処理のためのデータ共有と同期を簡素化し、データ競合などの問題を回避することができます。

`Sendable`プロトコルの主な特徴は以下の通りです：

1. 送信可能性: `Sendable`プロトコルを準拠する型は、非同期コンテキストを介して送信（送出）できることを表します。このプロトコルを準拠する型のインスタンスは、非同期タスク内で非同期に移動したり、他の非同期タスクに渡したりすることができます。

2. データ競合の回避: `Sendable`を使用することで、並行してアクセスされるデータへの安全なアクセスを提供します。複数の非同期タスクが同時にデータにアクセスしても、データ競合が発生することがなくなります。

以下は、`Sendable`プロトコルの使用例です：

```swift
@Sendable
func processItems(items: [Int]) async {
    for await item in items {
        // 非同期処理
        print(item)
    }
}

async {
    await processItems(items: [1, 2, 3])
    await processItems(items: [4, 5, 6])
}
```

上記の例では、`processItems`関数に`@Sendable`属性が付与されています。この関数は非同期に処理され、`items`の要素を非同期に処理します。`processItems`関数は非同期コンテキスト内で安全に呼び出されるため、複数の非同期タスクから同時に呼び出されても競合や問題は生じません。

`Sendable`は非同期処理や並行性をサポートする上で重要な役割を果たします。しかし、すべての型が`Sendable`を準拠するわけではなく、データの共有やアクセスの安全性を考慮して使用する必要があります。
</div></details>

## 2. precondition()
```swift
public func precondition(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line)
```
`assert()`とほぼ一緒だな。
ただし、`precondition()`はリリースビルドでも有効なので注意。


## 3. assertionFailure()
```swift
@inlinable public func assertionFailure(_ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line)
```
これも`assert()`と似ていて、playgroundやデバッグ時にプログラムの実行を止めて`message`をprintする。
ただし`assert()`と異なり`condition`を持たないので、いつエラー投げるかは`assertionFailure()`の外側で定義する。

## 4. preconditionFailure()
```swift
public func preconditionFailure(_ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line) -> Never
```
`assertionFailure()`に似ている。
 `-O builds`（デフォルトのリリースビルドコンフィギュレーション）でもプログラムの実行を停止する。

## 5. fatalError()
```swift
public func fatalError(_ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line) -> Never
```
今日知りたかった`fatalError()`<br>
無条件でエラーぶん投げるから、使用には注意が必要そう。

# まとめ
自分の場合、前提を満たしていない場合にエラーを投げたいことが多かったから、条件を引数に渡せる `precondition()`を使うのが一番簡潔に書けそう。