Swift Digest
SE-0016 | Swift Evolution

Add initializers to Int and UInt to convert from UnsafePointer and UnsafeMutablePointer

Proposal
SE-0016
Authors
Michael Buckley
Review Manager
Chris Lattner
Status
Implemented (Swift 3.0)

01 何が問題だったのか

Swift 2.x 当時、IntUInt から UnsafePointer / UnsafeMutablePointer を作ることはできましたが、逆にポインタから整数を取り出す手段が標準ライブラリにありませんでした。そのため、システムプログラミング領域でよくある「ポインタを整数として扱う」操作がSwiftだけでは書けない状態でした。

高度なポインタ演算ができない

ポインタのアラインメントを確認したり、ポインタにタグビットを埋め込んだり、XOR 連結リスト(前後のノードのアドレスを XOR して1つのフィールドに詰め込むデータ構造)のようにポインタ同士を XOR したりといった操作は、いずれもポインタをいったん整数として扱う必要があります。UnsafePointer 自体には加減算程度しか定義されていないため、こうした演算を Swift で直接書く手段がありませんでした。

intptr_t / uintptr_t を取るC関数を呼べない

C の API には、引数や戻り値に intptr_t / uintptr_t(ポインタを格納できる幅の整数型)を使うものがあります。Swift からこれらの関数を呼ぼうとしても、UnsafePointer を対応する整数に変換する標準の方法がないため、一度 C 側にラッパーを書いてポインタと整数を変換してから呼ぶ、という回りくどい対応が必要でした。

システムプログラミング言語を標榜するからには、こうしたケースも C に頼らず Swift だけで簡潔に書けるようにしたい、という動機があります。

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

IntUInt に、UnsafePointer / UnsafeMutablePointer / OpaquePointer を受け取って「ポインタのビットパターンをそのまま整数として解釈する」イニシャライザを追加します。ラベルは bitPattern: で、ビットパターンをそのまま移し替えるだけのイニシャライザであることが名前から読み取れるようになっています。

追加されるイニシャライザ

Int / UInt それぞれに、3種類のポインタ型を受け取るイニシャライザが用意されます。UnsafePointer / UnsafeMutablePointer はジェネリックなので、要素型 T に対するオーバーロードになります。

extension UInt {
  init<T>(bitPattern: UnsafePointer<T>)
  init<T>(bitPattern: UnsafeMutablePointer<T>)
  init(bitPattern: OpaquePointer)
}

extension Int {
  init<T>(bitPattern: UnsafePointer<T>)
  init<T>(bitPattern: UnsafeMutablePointer<T>)
  init(bitPattern: OpaquePointer)
}

これらはポインタのアドレスをワード幅の整数としてそのまま取り出すだけで、実行時のコストはありません。既存の「整数からポインタを作る」側のイニシャライザと対をなす形になります。

使い方

たとえば XOR 連結リストで、前のノードのアドレスと現在のノードに保存された値を XOR して次のノードのアドレスを得る処理は、次のように書けるようになります。

struct XORLinkedList<T> {
  let address: UnsafePointer<T>

  func successor(_ predecessor: XORLinkedList<T>) -> XORLinkedList<T> {
    let next = UInt(bitPattern: address) ^ UInt(bitPattern: predecessor.address)
    return XORLinkedList(UnsafePointer<T>(bitPattern: next))
  }
}

UInt(bitPattern:) でポインタを整数に変換し、XOR を取ってから、対になる UnsafePointer.init(bitPattern:) で再びポインタに戻す、という流れです。アラインメントチェック(UInt(bitPattern: p) & (alignment - 1) == 0)やタグ付きポインタのような処理も、同じ要領で書けます。

また、intptr_t / uintptr_t を引数に取る C 関数を呼ぶ場合も、Int(bitPattern:) / UInt(bitPattern:) でポインタを整数に変換してそのまま渡せるようになり、C 側のラッパーを書く必要がなくなります。

安全性について

これらのイニシャライザが扱うのは本質的にアンセーフな操作です。Swift で書けるようになっても安全性が上がるわけではなく、C で同じことをするのと同等の責任が利用者側にあります。Swift を使うことで「C よりきれいに書ける」だけで、ポインタ演算の正しさ自体は利用者が担保する必要がある点に注意してください。