Swift Digest
SE-0076 | Swift Evolution

Add overrides taking an UnsafePointer source to non-destructive copying methods on UnsafeMutablePointer

Proposal
SE-0076
Authors
Janosch Hildebrand
Review Manager
Chris Lattner
Status
Implemented (Swift 3.0)

01 何が問題だったのか

UnsafeMutablePointer には、別のポインタが指すメモリから要素を非破壊的にコピーするためのメソッドがいくつかありました。具体的には次の3つで、いずれも source として UnsafeMutablePointer<Pointee> を受け取る形になっていました。

func assignFrom(source: UnsafeMutablePointer<Pointee>, count: Int)
func assignBackwardFrom(source: UnsafeMutablePointer<Pointee>, count: Int)
func initializeFrom(source: UnsafeMutablePointer<Pointee>, count: Int)

assignFromassignBackwardFrom は初期化済みメモリへの代入、initializeFrom は未初期化メモリへの初期化を行うもので、コピー元のメモリを変更しない(非破壊的)点が共通しています。

問題は、コピー元が UnsafePointer である場合に、これらのメソッドを素直に呼び出せないことでした。source の型が UnsafeMutablePointer に固定されているため、呼び出し側で UnsafeMutablePointer へのキャストを挟む必要がありました。

let source: UnsafePointer<Int> = ...
let destination: UnsafeMutablePointer<Int> = ...

// 提案前は明示的なキャストが必要
destination.assignFrom(UnsafeMutablePointer(source), count: count)

非破壊的なコピーではコピー元のメモリは読み取られるだけなので、UnsafePointer(読み取り専用)を渡しても本来は安全です。それにもかかわらず、型だけを合わせるために UnsafeMutablePointer への明示的なキャストを書かされるのは、視覚的なノイズが増えるだけでなく、コードを読む側に「ここで可変性を付け足している」という警戒感を与える原因にもなっていました。

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

UnsafeMutablePointer に、source として UnsafePointer<Pointee> を受け取るオーバーロードを追加します。既存の UnsafeMutablePointer を受け取るメソッドはそのまま残るため、呼び出し側はコピー元の型に応じて自然にどちらかが選ばれます。

追加されるのは次の3メソッドです。

/// 初期化済みメモリに、source から count 個の値を先頭から順に代入する。
public func assignFrom(source: UnsafePointer<Pointee>, count: Int)

/// 初期化済みメモリに、source から count 個の値を末尾から順に代入する。
/// 代入先のメモリが source の範囲と重なり、かつ代入先の方が後方にある場合に使う。
/// 要件: source が self より前にあるか、source が self + count より後ろにあること。
public func assignBackwardFrom(source: UnsafePointer<Pointee>, count: Int)

/// 未初期化メモリに、source から count 個の値をコピーして初期化する。
/// 前提: self が指すメモリは未初期化であること。
/// 要件: self と source のメモリ範囲は重なっていないこと。
public func initializeFrom(source: UnsafePointer<Pointee>, count: Int)

これにより、UnsafePointer をコピー元として渡す際にキャストが不要になります。

let source: UnsafePointer<Int> = ...
let destination: UnsafeMutablePointer<Int> = ...

// 提案後はそのまま渡せる
destination.assignFrom(source, count: count)

なぜ単に既存メソッドを UnsafePointer 版に置き換えないのか

UnsafeMutablePointer から UnsafePointer への暗黙変換を利用すれば、既存メソッドを削除して UnsafePointer 版のみを残すことでも同じ呼び出しは書けます。しかし、明示的なオーバーロードを両方用意する方がドキュメントやコード補完の観点で意図が明確になるため、オーバーロードを追加する形が採用されています。

なお本提案は純粋に追加的な変更で、既存のコードに影響はありません。