この記事の要点
- システムコールや低レベルな基本型に対して、Swift らしい型安全なインターフェースを提供するライブラリ Swift System がオープンソース化され、あわせて Linux サポートも追加されました。当初は Apple プラットフォーム向けでしたが、Swift がサポートするすべてのプラットフォームにおける低レベルシステムインターフェースの共通の置き場所となることを目指しています。
- C から取り込まれる弱い型付けのシステムインターフェース(
Int32の file descriptor、OR 結合したフラグ、errnoの手動チェックなど)は誤りを招きやすいものでした。Swift System は、専用の型・option set・エラーのthrow・FilePathなどを使い、これらの落とし穴を取り除きます。 - Swift System は cross-platform ライブラリではなく multi-platform ライブラリです。プラットフォームごとに、その OS のインターフェースを忠実に反映した API を提供します。
何が発表されたのか
Swift System は、2020 年 6 月に Apple プラットフォーム向けに導入された、システムコールや低レベルな基本型に対して Swift らしいインターフェースを提供するライブラリです。今回これがオープンソース化され、あわせて Linux サポートも追加されました。最終的には、Swift がサポートするすべてのプラットフォームにおける低レベルシステムインターフェースの単一の置き場所となることが構想されています。
C から取り込まれたインターフェースの問題
今日のほとんどの OS は、数十年前から存在する C で書かれたシステムインターフェースを何らかの形で提供しています。これらの API は Swift から直接使えますが、C から取り込まれた弱い型付けのインターフェースは誤りを招きやすく、扱いづらいものです。
たとえば UNIX 系 OS(Linux や Apple プラットフォームなど)で使える open システムコールは、Swift には次の 2 つのグローバル関数として取り込まれます。
func open(_ path: UnsafePointer<CChar>, _ oflag: Int32) -> Int32
func open(_ path: UnsafePointer<CChar>, _ oflag: Int32, _ mode: mode_t) -> Int32
これらの関数には、Swift の表現力や型安全性を活かせていない問題がいくつもあります。
- file descriptor も、オプション・コマンド・
errnoといったその他の値も、すべて単なるInt32として取り込まれます。 oflag引数は、実際には「ちょうど 1 つのファイルアクセスモード」と「任意個のフラグ」を論理 OR で結合したものですが、そのことがoflagの型には表れていません。- 呼び出し側は、エラーを表す負の戻り値を自分でチェックし、エラーが起きていればグローバル変数
errnoの値を確認して原因を知る必要があります。さらに、シグナルによって中断され得るシステムコールでは、EINTRエラーをチェックしてリトライするループを自分で書く必要があります。 - ファイルパスは管理されないポインタです。
Array<CChar>のような管理されたオブジェクトから導出した場合、呼び出し側はその配列が常に null 終端されていることを保証しなければなりません。
これらの意味的なルールは API のシグネチャに表れないため、言語が利用者を正しい使い方へ導くことができません。
Swift らしいインターフェースによる解決
System モジュールは、Swift の言語機能を活用してこれらの誤りの余地を取り除きます。たとえば open システムコールは、FileDescriptor 名前空間の中で、デフォルト引数付きの static 関数として定義されています。
extension FileDescriptor {
public static func open(
_ path: FilePath,
_ mode: FileDescriptor.AccessMode,
options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(),
permissions: FilePermissions? = nil,
retryOnInterrupt: Bool = true
) throws -> FileDescriptor
}
C 版の open と比べると、次のような重要な違いがあります。
Systemは専用の raw representable な構造体や option set を一貫して使います。これらの強い型はコンパイル時に誤りを捕捉でき、弱い C の型との相互変換も簡単です。- エラーは標準の言語機構を使って
throwされ、見落とすことができません。さらに、シグナルで中断され得るシステムコールはデフォルトがtrueのretryOnInterrupt引数を取り、失敗時に自動でリトライします。この 2 点により、エラー処理とシグナル処理が大幅に簡潔になります。 FilePathは管理された null 終端のバイト列で、ExpressibleByStringLiteralに適合します。UnsafePointer<CChar>よりはるかに安全に扱えます。
結果として、Swift らしく読み書きできるコードになります。たとえば次のコードは、文字列リテラルからファイルパスを作り、それを使ってログファイルを開いて追記します。
let message: String = "Hello, world!" + "\n"
let path: FilePath = "/tmp/log"
let fd = try FileDescriptor.open(
path, .writeOnly, options: [.append, .create], permissions: .ownerReadWrite)
try fd.closeAfter {
_ = try fd.writeAll(message.utf8)
}
multi-platform ライブラリとしての位置づけ
System は cross-platform ライブラリではなく multi-platform ライブラリ です。つまり、サポートする各プラットフォームごとに、基盤となる OS インターフェースを忠実に反映した別々の API と挙動を提供します。1 回の import で、ターゲット OS に固有のネイティブなプラットフォームインターフェースが取り込まれます。
当面の目標は、SwiftNIO や Swift Package Manager のような cross-platform なライブラリ・アプリケーションの構築を容易にすることです。System を使っても cross-platform な抽象化のための #if os() 条件分岐がなくなるわけではありませんが、プラットフォーム固有の部分をより安全に、より表現力豊かに書けるようになります。
今後の見通し
発表時点で示されていた今後の構想であり、実現を約束するものではありません。
Swift System はまだ初期段階で、含まれるシステムコール・基本型・便利機能はごくわずかです。API のカバー範囲を広げる取り組みの一環として、Swift Package Manager への Swift System の採用が進められる予定でした。これには FilePath の機能強化や、当時発表されたばかりの Windows で動く Swift のサポート追加が含まれます。