唯一変わったのは、そのすべて
「唯一変わったのは、
Type?
Swiftの最大の特長は何かと問われたら、
次のようなDictionary
があったとします。
var supportedLanguages = [
"C" : 1,
"ObjectiveC" : 2,
]
次はなんとprint
するでしょうか?
print(supportedLanguages["C"])
1
ではなく、Optional(1)
ですね。では次は?
print(supportedLanguages["Swift"])
nil
となります。
今度はOptional(1)
ではなく1
となります。この挙動を、supportedLanguages
の型は[String:Int]
です。つまりString
を添字にすると、Int
が返ってくるデータ型なのですが、String
を添字には何を返したらよいでしょう? クラッシュするかnil
か値を返す」supportedLanguages[k]
の型は、Int
ではなくOptional<Int>
つまりInt?
ということになります。
ところでSwiftには、enum
があります。Optional型をenum
で表現するとどうなるでしょうか? こんな感じでしょうか。
enum Optional<T> {
case Nil
case Some(T)
}
Swiftの実装は、[String, Int]
が実はDictionary<String,Int>
の構文糖衣であるように、Int?
というのはOptional<Int>
の構文糖衣に過ぎないのです。
There's more than one way to fail
以上を踏まえて、
var language = "C"
if let i = supportedLanguages[language] {
print(i)
} else {
print("Swift is not supported");
}
Optional(1)
ではなく、1
と表示されます。i
の型はInt?
ではなくInt
で、if
に続く{}
の中では100%例外なくiはInt
であることが保証されている一方、else
に続く{}
の中ではsupported Languages[language]
がnil
だったことが100%例外なく保証されているわけです。これがSwiftにおけるエラー処理の基本でした。OptionalDictionary
のような動的に扱いたい型の扱いが動的言語なみに楽になったのです。
しかし、
SwiftのOptionalがenum
で実装されていることを知っていれば、SuperOptional
を定義してしまえばその問題は解決しそうです。
enum SuperOptional<E,T> {
case Error(E)
case Some(T)
}
func handleSuperOptional<E,T>(so:SuperOptio
nal<E,T>) {
switch(so) {
case let .Some(i):
print(i)
case let .Error(s):
print("Error:\(s)")
}
}
var so:SuperOptional<String, Int> =
.Some(42)
handleSuperOptional(so)
so = .Error("Not a number")
handleSuperOptional(so)
ところが、
![図1 総称型enumの動作 図1 総称型enumの動作](/assets/images/dev/serial/01/swift-introduction/0010/thumb/TH800_001.png)
見てのとおりSwift 2では期待どおり動いていますが、
Swift 1におけるOptional
はenum
で実装されていましたが、trycatch
もenumによって実現されています。
give it a try
というわけでSwift 2のtry catch
を実際に使ってみましょう。ここでは例題として、
SwiftのArray
の要素に範囲外の添字を与えると、
var ary = [0,1,2,3]
ary[4] // ここでクラッシュ
これはDictionary
とは異なる振る舞いです。
var dict = [0:0, 1:1, 2:2, 3:3]
dict[4] // nil
問答無用でクラッシュする代わりに、
Swift 2では、
enum ArrayError : ErrorType {
case RangeError
}
見てのとおり、ErrorType
型を継承したenum
です。次に、
extension Array {
func valueAtIndex(i:Int) throws ->
Element {
if self.count <= i {
throw ArrayError.RangeError
}
return self[i]
}
}
通常のfunc
と異なる点は2つ。1つは->
の前にthrows
というキーワードが追加されていること、throw ArrayError.
していること。
あとはこれを使うだけ。
var ary = [0,1,2,3]
do {
var v:Int
v = try ary.valueAtIndex(0)
v = try ary.valueAtIndex(4)
} catch {
print("Array out of range")
}
たしかに今度はクラッシュせず、try
ではなくdo
で始まっています。そしてtry
はary.
の前についています。try
を取り除くとどうなりましたか?
![図2 trycatchのエラー 図2 trycatchのエラー](/assets/images/dev/serial/01/swift-introduction/0010/thumb/TH800_002.png)
Javacatch
は特定のエラーだけを捕まえることもできます。たとえば次のようにコードを書き換えてみましょう。
enum ArrayError : ErrorType {
case OutOfBounds
case NegativeBounds
}
extension Array {
func valueAtIndex(i:Int) throws ->
Element {
if i < 0 {
throw ArrayError.NegativeBounds
}
if self.count <= i {
throw ArrayError.OutOfBounds
}
return self[i]
}
}
var ary = [0,1,2,3]
do {
var v:Int
v = try ary.valueAtIndex(0)
v = try ary.valueAtIndex(-1)
} catch ArrayError.NegativeBounds {
print("Array Index must be zero or
lager")
} catch ArrayError.OutOfBounds {
print("Array Index too large")
} catch {
print("Unknown Error")
}
例外を例外扱いしないSwift
さらにSwiftならではの特長として、catch
でまとめて捕まえるのではなく、if let
やguard
で捕まえることもできます。
if let v = try? ary.valueAtIndex(4) {
print(v)
} else {
print("Array out of range")
}
もしくは
guard let v = try? ary.valueAtIndex(3) else
{
print("Array out of range")
}
まとめると、
TypeError
を継承したエラー型を定義- エラーを起こしうる
func
には->
の前にthrows
をつける(エラーを起こしたら定義したエラーを throw
) - エラーを起こしうる関数/
メソッドは try
する(エラーは catch
だけではなくif let
やguard
で使うこともできる)
このようにSwift 2のtry catch
機構は、enum
の自然な拡張として実現されている点が実に特長的です。構文の視点で見るとdo catch
は
次号は
「なるべく一般的かつ包括的に」。それがさらに反映されているのが、
次回は、
本誌最新号をチェック!
Software Design 2022年9月号
2022年8月18日発売
B5判/
定価1,342円
- 第1特集
MySQL アプリ開発者の必修5科目
不意なトラブルに困らないためのRDB基礎知識 - 第2特集
「知りたい」 「使いたい」 「発信したい」 をかなえる
OSSソースコードリーディングのススメ - 特別企画
企業のシステムを支えるOSとエコシステムの全貌
[特別企画] Red Hat Enterprise Linux 9最新ガイド - 短期連載
今さら聞けないSSH
[前編] リモートログインとコマンドの実行 - 短期連載
MySQLで学ぶ文字コード
[最終回] 文字コードのハマりどころTips集 - 短期連載
新生「Ansible」 徹底解説
[4] Playbookの実行環境 (基礎編)