【Swift初心者のための】オプショナル型と if let は何のためにあるの？

Swift の初心者向けの解説を読んでいると、次のような説明を見かけることがあります。

> `Int?` のように `?` を付けるとオプショナル型になります。オプショナル型の値を取り出すには `!` を付けて強制アンラップします。

まちがってはいないのですが、 **この説明では誤解を生みかねません** 。オプショナル型が何のためにあるのかに立ち戻って、適切な使い方を説明します。

## オプショナル型がないと何が起こる？

オプショナル型は何のためにあるのでしょうか。もし Swift にオプショナル型がなかったらどのような問題が起こるでしょうか。

例として、ユーザーに年齢を入力してもらい、成人か確認するコードを考えてみましょう。 18 歳以上の場合には `成人です。` と表示します。

<img width="964" alt="text-field.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47085/d0ba34f8-c4a9-b8ed-fa81-0bb81cc9a103.png">

年齢（ age ）が 18 歳以上かチェックするには `if age >= 18` のような条件を書きます。しかし、テキストフィールドに入力された年齢は文字列なので `String` 型です。 `age >= 18` のように比較するには、 `age` が `String` 型ではなく `Int` 型である必要があります。

`String` を `Int` に変換するには↓のように書きます（ `text` は `String` 型とします）。

```swift
let age = Int(text)
```

`Int(text)` が `text` を `Int` 型に変換し、その結果が `age` に代入されます。たとえば、テキストフィールドに `"20"` という文字列が入力されていたら `age` の値は整数の `20` になります。

<img width="964" alt="text-field-20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47085/57cb1dfa-5b02-b9f5-7e09-7fa885694a75.png">

しかし、もし `"ABC"` という文字列が入力された場合はどうなるでしょうか。 `"ABC"` を `Int` （整数）に変換することはできません。そんな場合は、変換に失敗したことを表す `nil` になります。

<img width="964" alt="text-field-abc.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47085/8046a5c4-3baa-1ac4-faf0-7f8025e2ef85.png">

Swift では `Int` 型の変数や定数に `nil` を代入することができません。 `nil` を代入したい場合は `?` を付けて `Int?` 型とします。これがオプショナル型です。 `Int?` 型なら `Int` 型の値か `nil` を、 `String?` 型なら `String` 型の値か `nil` を代入することができます。↑のコードでは `age` は `Int?` 型になります。

しかし、オプショナル型がない言語では、どんな型の変数にも `nil` を代入することができます。 `Int` 型でも `String` 型でも、常に `nil` が代入されているかもしれません。もしオプショナル型がなければ、↑のコードの `age` は `Int` 型（ただし `nil` かもしれない）ということになるでしょう。

このとき、成人かチェックして `成人です。` と表示するコードはどのように書けば良いでしょう。たとえば、↓のように書いたとします。

```swift
// オプショナル型が『ない』とき
let age: Int = Int(text)
if age >= 18 {
    print("成人です。")
}
```

オプショナル型がないなら、↑のコードはコンパイルに成功し、実行することができるでしょう。 `age` は `Int` 型なので、 `age >= 18` のような比較は型の上では問題ありません。

しかし、テキストフィールドに `"ABC"` のような文字列が入力されたらどうなるでしょうか。 `age` は `nil` なので、 `nil >= 18` という比較をすることになります。 `nil` と `18` の大小関係を比較することはできないので、実行時エラーになってプログラムはクラッシュしてしまいます（実際に、オプショナル型を持たない言語ではそのような挙動になります）。

`"ABC"` のような文字列を入力された場合でもプログラムがクラッシュしないようにするには、 `age >= 18` をする前に `age` が `nil` でないことをチェックしておきます。

```swift
// オプショナル型が『ない』とき
let age: Int = Int(text)
if age != nil { // nil でないことをチェック
    if age >= 18 {
        print("成人です。")
    }
}
```

このとき、 `if age != nil` の `{ }` の中には `age` が `nil` でないときしか入りません。 `age` が `nil` でないことがわかっているので、 `age` を使っても安心です。 `age >= 18` でクラッシュすることもありません。

オプショナル型がない場合に問題となるのは、 **うっかり `age != nil` のようなチェック（ `nil` チェックと呼びます）を書き忘れてしまったときに、プログラムがクラッシュしてしまう** ことです。もしコンパイラが「 `nil` チェックを忘れてるよ！」と教えてくれたらミスが防げてうれしいですね。それをやってくれるのがオプショナル型です。

## オプショナル型があるとどうなる？

では、オプショナル型があるとコードはどう変わるでしょうか。まずは、うっかり `nil` チェックを忘れてしまった場合を見てみましょう。

```swift
// オプショナル型が『ある』とき
let age: Int? = Int(text)
if age >= 18 { // ⛔ コンパイルエラー
    print("成人です。")
}
```

まず、 `age` の型が `Int` から `Int?` に変わりました。 `Int(text)` は失敗して `nil` を返すかもしれないので、 `age` の値は `Int` 型または `nil` です。 `Int` 型の変数・定数に `nil` を代入することはできないので、 `age` は `Int` 型ではなく `Int?` 型でなければなりません。

オプショナル型を導入して `Int` 型と `Int?` 型を区別することで、コンパイラがうっかりミスに気付くことができます。 `age >= 18` というコードは、 `Int?` 型の `age` と `Int` 型の `18` を比較しています。 `age` は `nil` かもしれないので、 `age >= 18` という比較はうまくいかないかもしれません。コンパイラはこのコードはおかしいと判断して、コンパイルエラーにします。

プログラムを実行するためには必ずコードをコンパイルしないといけないので、コードを書いた人はプログラムを実行する前にミスに気付くことができます。コンパイルエラーを元に、自分の書いたコードの何がおかしかったんだろうと考えられます。そして、「 `Int?` 型の `age` と `Int` 型の `18` を比較したのがよくなかったんだ！」と気付くことができるわけです。

ミスに気付いたら、今度はコードを↓のように変更するでしょう。 `nil` チェックをして `age` が `nil` でないことを確かめます。しかし、それでもコンパイルエラーになってしまいます。

```swift
// オプショナル型が『ある』とき
let age: Int? = Int(text)
if age != nil {
    if age >= 18 { // ⛔ コンパイルエラー
        print("成人です。")
    }
}
```

今度はきちんと `age != nil` で `nil` チェックをしています。 `age >= 18` の時点では `age` が `nil` でないことは確実です。どうしてコンパイルエラーになってしまうのでしょうか。

コンパイラがチェックしているのは `age >= 18` という式の型だけです。 `age != nil` をチェックしたからといって、 `age` の型が `Int?` から `Int` に変わるわけではありません。 `if age != nil` の `{ }` の中でも、 `age` の型は `Int?` のままです。そのため、 `age >= 18` は相変わらず `Int?` と `Int` を比較していることになり、コンパイルエラーになってしまうのです。

では、どうすればよいのでしょうか。ここで強制アンラップ（ **_Forced Unwrapping_** ）の出番です。 `!` を使って強制アンラップすれば、コンパイルして実行することができるようになります。 **（ :warning: 後ほど説明しますが、これは良い方法ではありません。）**

```swift
// オプショナル型が『ある』とき
let age: Int? = Int(text)
if age != nil {
    if age! >= 18 {
        print("成人です。")
    }
}
```

`!` （強制アンラップ）は、オプショナル型を無理やりオプショナルでない型に変換します。 `Int?` の `age` に対し、 `age!` と書けば `Int` 型に変換することができます。しかし、もし `age` が `nil` だった場合には実行時エラーになってプログラムがクラッシュします。

↑のコードでは、事前に `age != nil` をチェックしているので `age!` でクラッシュすることはありません。 `"ABC"` のような文字列が入力されてもクラッシュせずに動くプログラムができました。めでたし、めでたし。

といきたいところですが、↑のコードにも問題があります。このコードを書いた人とは別の人が読む場合、（もしくは、すっかりコードの内容を忘れてしまった半年後の自分が読む場合、） `age!` を見てドキッとします。 **この `age` は本当に `nil` にならないのか、うっかりチェックを忘れてしまっていて、実はこのコードはクラッシュする可能性があるんじゃないか、と不安に思うことでしょう。そして、万が一チェックを忘れているとプログラムはクラッシュする可能性があります。** 幸い、このコードでは前の行を見ると `if age != nil` とチェックしていることが確認できるので、 `age` は `nil` にならないと安心することができます。

しかし、 `nil` チェックがいつも強制アンラップ（ `!` ）の近くに書かれているとは限りません。そもそも、 `nil` チェックをしているので、この `age! >= 18` の行では `age` は絶対に `nil` でないわけです。コンパイラが `age` を `Int?` 型ではなく `Int` 型として扱ってくれればいいのに！そうすれば最初から `!` を心配をする必要はないはずです。

つまり、僕らが必要としているのはただの `nil` チェックではなく、 `nil` チェックと同時に `Int?` 型から `Int` 型に変換してくれるような処理なのです。それをやってくれるのが `if let` です。

## `if let` は何のためにあるのか

`if let` を使えば、 `nil` チェックと `Int?` → `Int` の変換を同時に行うことができます。

```swift
// if let を使う場合
let age: Int? = Int(text)
if let age2: Int = age {
    if age2 >= 18 {
        print("成人です。")
    }
}
```

↑のコードでは、新しい `Int` 型の定数 `age2` を作って、そこに `age` の値を代入しようとしています。 `age` は `Int?` 型なので `nil` の可能性があります。 `nil` を（ `Int` 型の） `age2` に代入することはできません。

そのため、↑のコードでは `age` が `nil` でなかった場合だけ `age2` への代入が行われます。もし `age` が `nil` だったら、 `{ }` の中身は実行されません。そのような条件分岐をするからこそ `let` に `if` が付いて `if let` なのです。

`if let` で宣言された `age2` は、 `{ }` の中でだけ使うことができます[^1]。 `age2` は `Int` 型なので、 `age2 >= 18` のように比較をしても `Int` と `Int` の比較なので問題ありません。

`if let` がやっていることを整理すると、次の二つになります。

- `age` が `nil` でないことのチェック（ `nil` チェック）
- `Int?` 型の `age` を `Int` 型の `age2` に変換

**`if let` を使えば、強制アンラップ（ `!` ）のようにプログラムがクラッシュする心配もなく、またうっかり `nil` チェックを忘れてしまうこともなくコードを書けるのです。オプショナル型のおかげで安全なコードが書けるようになりました。**

なお、 `if let` の正式な名前は **_Optional Binding_** と言います[^2]。 `if let` で通じますが、一応正式名称も覚えておきましょう。また、滅多に使うことはありませんが、 `if let` だけでなく `if var` を書くこともできます。もし定数ではなく変数であってほしい場合には `if var` が使えます。

## よりスマートな書き方

ところで、↑のコードはもっとスマートに書くことができます。まず、 `if let age2: Int = age` の `age2` が `Int` 型なのはわかりきっています。 `: Int` がなくてもコードの読みやすさは変わらないでしょう。 `if let age2 = age` で十分です（もちろん、型を書くことでコードが読みやすくなる場合は型を書いた方がです）。

また、 `age2` を使わずに、 `if let age = age` と書くことも可能です。 Swift ではスコープ（ `{ }` の階層）が違えば同じ名前の変数・定数を作ることができます。たとえば、↓のようなコードを書くこともできます。

```swift
let a = 2
do {
    let a = 3
    print(a) // 3
}
print(a) // 2
```

このように、内側のスコープ（ `{ }` ）で外側のスコープと同じ名前の変数・定数を宣言して、外側の変数・定数を隠してしまうことを **_シャドーイング_** と呼びます。

`if let` は一見 `{ }` の外側で宣言されているように見えますが、 `{ }` の中に所属します。そのため、 `age` を _シャドーイング_ して↓のように書けるわけです。

```swift
// if let を使う場合のスマートな書き方
let age: Int? = Int(text)
if let age = age { // シャドーイング
    if age >= 18 {
        print("成人です。")
    }
}
```

さらに、スマートに書くこともできます。↑のコードでは `if let age = age` と `if age >= 18` が二つ続いてネストしています。この二つの条件を `,` で区切って並べて、一つの `if` 文にまとめることができます。

```swift
// if let を使う場合のスマートな書き方
let age: Int? = Int(text)
if let age = age, age >= 18 {
    print("成人です。")
}
```

このような、 `if let` してから値をチェックするは頻出パターンです。習得しておくと役に立つ機会も多いでしょう。

## `!` を使うべきとき

強制アンラップ（ `!` ）は危険なもので、使わない方が良いのでしょうか。そうではありません。 `!` を使うべきケースもあります。

たとえば、テキストフィールドに数字しか入力できない制限がかかっていたらどうでしょうか。 `"ABC"` のような文字列を入力することはできません。もし、常に有効な整数しか入力できないのであれば、 `Int(text)` が失敗して `nil` を返すことはありません。

しかし、それがわかっていても、 `Int(text)` が返すのは型の上では `Int?` です。これはどうしようもありません。型は万能ではないので、絶対に起こらないけど型では表現できないこともあります。そんな場合には `!` を使うことでコードを簡潔にできます[^3]。

```swift
// text に有効な整数しか入力できない場合
let age: Int = Int(text)! // 強制アンラップ
if age >= 18 {
    print("成人です。")
}
```

ただし、「絶対に有効な整数しか入力できない制限」の実装にバグがあると、↑のコードはクラッシュを引き起こす可能性があります。 `!` は絶対に `nil` にならないと確信できる場合のみ使うようにしましょう。

## コード全体

<details><summary>テキストフィールドの作成や <code>text</code> の宣言なども含めたコンパイル可能なコード全体</summary><div>

```swift
import SwiftUI

struct ContentView: View {
    @State var text: String = ""
    
    var body: some View {
        VStack {
            TextField("年齢", text: $text)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .keyboardType(.numberPad)
                .frame(maxWidth: 200)
                .padding(20)
            Button.init("決定") {
                let text: String = self.text
                let age: Int? = Int(text)
                if let age = age, age >= 18 {
                    print("成人です。")
                }
            }
        }
    }
}
```

↑のコードは [SwiftUI](https://developer.apple.com/tutorials/swiftui/tutorials) を使って書かれています。ただし、 SwiftUI を正しく理解するためには Swift についての深い知識（ [Property Wrappers](https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md), [Key Path Member Lookup](https://github.com/apple/swift-evolution/blob/master/proposals/0252-keypath-dynamic-member-lookup.md), [Function builders](https://github.com/apple/swift-evolution/blob/9992cf3c11c2d5e0ea20bee98657d93902d5b174/proposals/XXXX-function-builders.md) など）が必要とされます。個人的には初心者の間は SwiftUI よりも UIKit を使うことをオススメします。

</div></details>

## まとめ

- オプショナル型がないとうっかり `nil` チェックを忘れてしまうかもしれない。 `nil` チェックを忘れるとプログラムはクラッシュしてしまう可能性がある。
- 強制アンラップ（ `!` ）を使ったコードは、そこだけを見て安全なのか判断できない。正しく `nil` チェックが行われていないと強制アンラップは失敗しクラッシュする。
- `if let` は `nil` チェックとアンラップを同時に行う。 `nil` チェック忘れを防止しながら、安全にオプショナル型から値を取り出すことができる。
- `!` は絶対に使ってはいけないわけではない。型は万能でないので、型の上ではオプショナルだけど、絶対に `nil` にならないとわかっている場合には `!` を使っても良い。

## ステップアップ

- [SwiftのOptionalのベストプラクティス](https://qiita.com/koher/items/8b6156c8263b9b23c43c)
    - Swift のオプショナル型の使い方についてより深く学びたい場合
- [SwiftのOptional型を極める](https://qiita.com/koher/items/c6f446bad54442a28bf4)
    - オプショナル型の仕組みや概念についての理解をより深めたい場合

[^1]: 厳密には、同じ `if let` の条件式の中でも使うことができます。（例: `if let age = age, age >= 18 { ... }` ）
[^2]: 厳密には、 _Optional Binding_ は `if let` だけでなく、 `if var` や `guard let` 、 `guard var` も含みます。
[^3]: これは、スマホアプリのようにクライアントサイドで完結するケースを想定しています。サーバーサイドのプログラムの場合、クライアントサイドで入力できる値をどれだけ制限しても、リクエストで不正な値が送られる可能性を排除できません。サーバーサイドでは常に不正な値を想定して処理を記述する必要があります。