Swift Digest
SE-0072 | Swift Evolution

Fully eliminate implicit bridging conversions from Swift

Proposal
SE-0072
Authors
Joe Pamer
Review Manager
Chris Lattner
Status
Implemented (Swift 3.0)

01 何が問題だったのか

Swift では、String / Array / Dictionary / Set といったSwiftの値型と、NSString / NSArray / NSDictionary / NSSet といった対応するObjective-Cの型との間で、橋渡し(bridging)のための暗黙の型変換が歴史的に認められていました。たとえば String を期待する引数に NSString を渡したり、その逆を行ったりしてもコンパイラが自動で変換してくれる、というものです。

暗黙変換が残っていた背景

Swift 1.2 では、これらの暗黙変換をすべて取り除き、代わりに as による明示的なキャスト(例: nsStrObj as String)を利用者に書いてもらう方針が検討されていました。しかし当時のコンパイラが未アノテーションのObjective-C APIを取り込む際、[NSObject : AnyObject] のような弱い型の辞書を返してくることが多く、次のような普通のコードまで書けなくなってしまう問題がありました。

var s: NSAttributedString
let SomeNSFontAttributeName: String // import結果としてString型

let attrs = s.attributesAtIndex(0, effectiveRange: nil) // [NSObject : AnyObject]
let fontName = attrs[SomeNSFontAttributeName]
// String から NSString への暗黙変換がないとコンパイルエラー

そのためSwift 1.2では妥協として、「Objective-Cの型からSwiftの値型への変換(例: NSStringString)だけは明示的なキャストを必須とし、逆方向(StringNSString など)は従来どおり暗黙に許す」という非対称なルールが採用されました。

非対称なルールが残したもの

この状況には以下のような問題が残っていました。

  • 型システムに、サブタイプ関係にない型同士の暗黙変換という特別扱いが残り続ける。
  • Foundation を import しただけで型検査の結果が微妙に変わる、コンパイラ内部のアドホックなルールが必要になる。
  • 利用者からは「いつ暗黙変換が起きているのか」が見えにくく、ユーザーモデルが分かりにくくなる。

加えてSwift 3では、Objective-Cの軽量ジェネリクス対応(SE-0057)やAPIインポートの改善により、かつては [NSObject : AnyObject] となっていた辞書が [String : AnyObject] として適切に取り込まれるようになりました。暗黙変換に頼らなくても、Objective-C APIがSwiftの値型で自然に表現されるようになったのです。こうした前提の変化を受けて、暗黙の橋渡し変換を完全に取り除く準備が整いました。

02 どのように解決されるのか

Swift 3 では、Swiftの値型とそれに対応するObjective-Cの型との間の暗黙変換を、両方向とも完全に廃止 します。残っていた「Swiftの値型からObjective-Cの型への暗黙変換」も許されなくなり、どちらの方向でも as による明示的なキャストが必要になります。

明示キャストが必要になる例

たとえば NSString を引数に取るAPIへ String を渡すような場面では、これまで暗黙変換で通っていたコードが次のようにエラーになります。

func takesNSString(_ s: NSString) { /* ... */ }

let swiftString: String = "hello"
takesNSString(swiftString) // error: Swift 3 以降は暗黙変換されない

明示的に as を書けばそのまま動きます。

takesNSString(swiftString as NSString) // OK

逆方向、すなわち NSStringString が必要な場所で使いたい場合も同様に明示キャストが必要です(こちらはSwift 1.2の時点で既に明示必須になっていたルールの継続です)。

func takesString(_ s: String) { /* ... */ }

let nsString: NSString = "hello"
takesString(nsString as String) // 以前から明示キャストが必要

対象となる橋渡しの組み合わせは、StringNSStringArrayNSArrayDictionaryNSDictionarySetNSSet など、Foundationが提供する標準的な bridged value type と Objective-C 型の組です。

得られるもの

この変更により、Swiftの型システムからサブタイプ関係にない型同士の暗黙変換という特別ルールが取り除かれます。その結果、

  • 型検査の挙動が Foundation の import 有無に左右されなくなる、
  • コンパイラ側の橋渡しに関する特殊処理を簡素化できる、
  • コード上で型変換が起きている箇所が as として常に見えるようになる、

といった効果が得られます。

既存コードへの影響

Swiftの値型と対応するObjective-C型の間で暗黙変換に頼っていたコードは、そのままではコンパイルが通らなくなります。対処は機械的で、該当箇所に as NSString / as String のような明示キャストを追加するだけです。Swift 3へのマイグレーションの文脈では、コンパイラのエラーメッセージに沿って順番に as を足していけば移行できる範囲の変更です。