[Swift]メモリ安全性:使用時に値が定義されていることを確認

LINEで送る
Pocket

C言語ではメモリ安全性(プログラムが不正なメモリアクセスを行わないこと)に苦労します。
その後の言語ではさまざまなアプローチでメモリ安全性を確保していますが、Swiftでももちろんメモリ安全性を保証しています。

A primary focus when designing Swift was improving the memory safety of the programming model. There are a lot of aspects of memory safety, so this post will start slow and cover a simple case: how to make sure that variables are initialized with a value before they are used.Swift blog

メモリ安全性:使用時に値が定義されていることを確認

Swift言語を設計する上で最も重点をおいたのが、プログラミングモデルでのメモリ安全性を向上することでした。メモリ安全性を測るのに多くの側面がありますが、本記事では単純なケース、値が使用される前に変数が初期化されていることをどうやって確認するか、について解説しています。

Swift言語でのアプローチ

コードが変数にアクセスする前に変数には有効な値が入っているということを開発者が確信できるとき、その変数は「安全」であると言えます。このような安全性を得るために、プログラミング言語はさまざまなアプローチをとります。C言語のように安全なプログラミング技法を採用するのを完全にプログラマの責任とする言語もあります。これは協力なアプローチですが、危険でいっぱいです。C++やObjective-Cではいかつかの必須パターンを強いることによって状況を改善しようとしています。定義時に初期化を強制するといった極端な措置をとる言語もあります。

Swift言語で採用された主要な技法は、コードのデータフロー解析を実行するのに私たちの高度なコンパイラを使用することです。コンパイラは変数が使用される前に初期化されているということを強制します。これはDefinitive Initialization(確定初期化、Definitive Assignmentとも)として知られる戦略ですが、JavaやC#、その他多くの言語でもこの技法を採用しています。Swift言語では広範囲の変数にこのアプローチの拡張版を使用しています。

この記事の最後では他の技法も紹介しています。その多くはSwift言語でも多かれ少なかれ採用しています。

ローカル変数の確定初期化

Swift言語ではさまざまな場面で確定初期化のルールを適用しますが、ローカル変数への使用がもっとも単純です。確定初期化は「暗黙的初期化」ルールより柔軟性を与えてくれます。例えば以下のコードが許されます。

var myInstance : MyClass  // Uninitialized non-nullable class reference

if x > 42 {
	myInstance = MyClass(intValue: 13)
} else {
	myInstance = MyClass(floatValue: 92.3)
}

// Okay because myInstance is initialized on all paths
myInstance.printIt()

ここでコンパイラはif式がmyInstanceを初期化することを保証し、初期化されていなメモリへのメソッド呼び出しはできないということを確認します。

確定初期化は強力なアプローチですが、信頼でき予測できる場合にだけ本当に便利です。これによって驚かされるのはもっと複雑な制御フローを扱っているときです。例えば以下のように。

var myInstance : MyClass

if x > 10 {
	myInstance = MyClass(intValue: 13)
}
// ...
if x > 42 {
	myInstance.printIt()
}

この例ではコンパイラはprintIt()を呼び出すときに「myInstanceという変数は初期化される前に使用された」ということを教えてくれます。コンパイラはif条件における述語論理式間の関係を追跡しないからです。このような特定のケースについては扱えるようコンパイラに教えることはできますが、すべてのケースにおいては不可能(停止問題のようなもの)です。だから私たちはコンパイラのルールを単純で予測可能なままにすることを選択しました。

Swift言語では変数を初期化するのは極めて簡単です。変数に初期値0を与えるのに、var x : Intと初期化されていない変数を宣言するよりもvar x = 0と宣言する方が短いです。Swift言語は可能な限り明示的に初期化することを好みます。必要になったときにinit()を使用して変数を初期化するのによりよい方法もいくつかあります。より詳しくはThe Swift Programming Language(Swift言語仕様)の「初期化」の章を参照してください。

他のアプローチ

Swift言語では確定初期化に加えて、言語仕様上の特定の範囲には別のアプローチも採用しています。他の言語でこの技法を使用しているかもしれませんが、この記事で簡単に触れておきたいと考えています。それぞれはいくつかの欠点がありますので、Swift言語での主要アプローチとしては使用されていません。

プログラマに安全性を委ねる
C言語の普及を考慮すると、開発者に安全性の向上を委ねることの良し悪しを理解することは大切です。不幸にもC言語において初期化されない値を使用すると予期せぬ動き(未定義な振る舞い)を引き起こし、落ちることがあります。C言語はプログラマが決してミスをしないということを期待しているのです。私たちの目標はSwfit言語を「何もしないでも安全に」することですので、このアプローチは一般的な使用ではすぐに却下されました。しかし、UnsafePointerのようなAPIではunsafety(安全でないこと)が絶対に必要なときに限り、明示的にオプトインして使用することができます。

暗黙的初期化
コンパイラが暗黙的に変数を初期化することによって、変数の安全性を保証できます。例えば、Objective−Cでは「ゼロ値」をインスタンス変数にセットしたり、C++ではコンストラクタを実行したりします。このアプローチについても入念に検討しましたが、最終的には広範囲な使用には向かないと結論づけました。

  • このアプローチはinit()が必要ないプロトコルやクラスへのnon-nullableな参照のように、適当な初期値がないときには機能しません。これらはSwift内ではよく使用されています。
  • 整数の0のようなプリミティブな型でさえ間違った値であることがあります。これはSwift言語で初期値を設定するのがとても簡単にしている理由の一つですが、コードをメンテナンスする人にとってわかりやすくするのと、省略したことによる潜在的なエラーを避けることです。例えば、センチネル(データの終了を示すデータ)に-1が使用されるときがあります。

nilを初期値とすることは、nullableの値にとっては正しい解だということには気づいていますか。Optionalのような型は自動的にnilを初期値として初期化されます。

定義時に初期化子を必須にする
変数が定義されるときに、常に初期値を与えるのを開発者の責任とします。var x : Intは初期化子として不適切ということになります。これは関数言語ではよく使われるアプローチですが、かなり厳密なプログラミングスタイルを強いるためにこの要求は重すぎる、自然なパターンを表現するのに邪魔になると考えました。

LINEで送る
Pocket

Tags:

コメントを残す