Swiftにnilという値は存在しない

  • 115
    いいね
  • 9
    コメント
この記事は最終更新日から1年以上が経過しています。

次のコードを実行すると、 anil が代入されると考える人は多いと思います。

let a: String? = nil

しかし、 Swift に nil という値は存在しません。存在するのは nil というリテラルだけです。 a に格納される値は Optional.None であって nil ではありません。

多くの言語ではリテラルはある型の値やインスタンスを表しますが、 Swift のリテラルは特定の型に紐付いていません。

本投稿では Swift のリテラルの仕組みについて説明し、 Swift に nil という値が存在しないことを示します。

Swiftと多くの言語でのリテラルの違い

多くの言語では、リテラルはそれ自体がどの型の値であるかを表しています。例えば、 Java では次のようにリテラルに対して型が一意に決定されます。

Javaのリテラル
123 // int
123L // long
123.0 // double
123.0F // float
true // boolean
'a' // char
"ABC" // String

しかし、Swiftではそうではありません。 123.0Float にもなりますし、 Double にもなります。

Swiftのリテラル
let f: Float = 123.0 // Float
let d: Double = 123.0 // Double

もっと言えば、 123.0他のどのような型のリテラルにでもなれます

例えば、次のような Extension を作れば String 型の変数に 123.0 というリテラルを代入できるようになります。

123.0をStringのリテラルにする
extension String : FloatLiteralConvertible /* Float リテラルを代入可能にする */ {
    public init(floatLiteral value: FloatLiteralType) {
        self = "\(value)"
    }
}

let s: String = 123.0 // s には "123.0" という文字列が代入される

Swift の 123.0Float 型のリテラルではなく FloatLiteralConvertible を実装したすべての型に代入可能なリテラルなのです。

Swift のリテラルは型を持ちません。対応する LiteralConvertible が実装された型の変数であれば何にでも代入できます。

NilLiteralConvertible

Swift の nil はリテラルの一種です。 123.0 を代入可能にする FloatLiteralConvertible があったように、 nil を代入可能にする NilLiteralConvertible というプロトコルがあります。

また、 Swift では String?Optional<String>シンタックスシュガーです(詳しくはこちら)。 let a: String? = nil のように 'nil' が代入できるのは、 Optional が特別な型だからではなく NilLiteralConvertible を実装しているからです。

Optional の他に NilLiteralConvertible を実装した型に UnsafePointer があります。そのため、次のようなコードが書けます。

let p: UnsafePointer<Int> = nil // Optional でないけど nil を代入できる
println(p) // 0x0000000000000000

pOptional でないのに nil が代入できてしまいました。このとき p に代入された nil と、 Optional に代入される nil には何の関係もありません。前者は C 言語で言うところの NULL ポインタで、後者は Optional.None です。 NilLiteralConvertible はもし nil というリテラルが代入されたらどうするかを定義するだけ で、それをどのような値として解釈することもできます。

極端な話、 nil が代入されたら 42 とするという意味のない定義をすることもできます。

extension Int : NilLiteralConvertible /* NilLiteralConvertible */ {
    public init(nilLiteral: ()) {
        self = 42 // 「生命、宇宙、そして万物についての究極の疑問の答え」
    }
}

let n: Int = nil
println(n) // 42

このことからも Swift の nil が値ではなく単なるリテラルであることがわかる と思います。

ちなみに、下記のコードで nil と出力されるのは anil が代入されているからではなく、 Optional.NonedebugDescription プロパティが "nil" という String を生成し、それを出力しているだけです。

let a: String? = nil // a に Optional.None が代入される
println(a) // Optional.None の debugDescription の値である "nil" が出力される
println(Optional<String>.None.debugDescription) // nil

色々なLiteralConvertible

その他にも、 Swift には次のような LiteralConvertible が宣言されています(これがすべてではないです)。

ArrayLiteralConvertible
BooleanLiteralConvertible
DictionaryLiteralConvertible
FloatLiteralConvertible
IntegerLiteralConvertible
StringLiteralConvertible

ArrayLiteralConvertibleDictionaryLiteralConvertibleArrayDictionary を自作(クラス版を作るとか)する場合などに、 IntegerLiteralConvertible は任意精度整数を作った場合などに便利そうです。

多くの言語では自作の便利クラスを作っても、組み込み型のようなリテラルのサポートをうけられずどうしても貧弱になってしまいます。 Swift のように LiteralConvertible 方式であれば、組み込み型と遜色のない便利な自作クラスを作ることができそうです( Swift には Operator FunctionSubscript もあるので、演算子を定義したり [i] でアクセスできるようにしたりして、組み込み型と同じように振る舞わせることもできます)。

まとめ

  • Swift には nil という値は存在せずリテラルがあるだけ
  • Swift のリテラルは型と結びついていない
  • LiteralConvertible がリテラルを値に変換する
  • nil の意味は NilLiteralConvertible の実装で決まる
    • Optional の場合は Optional.None
    • UnsafePointer の場合は 0x0000000000000000
  • 自作クラスに LiteralConvertible を実装すると便利そう