Swift Digest

Span を使った Data の操作

Data Manipulation with Spans

Proposal
SF-0042
Authors
Jeremy Schonfeld
Review Manager
Tina Liu
Status
Review: 2026-07-21...2026-07-28

このダイジェストはClaude Opus 4.7 / 4.8によって生成されたものです(License)。原文はこちら

01 何が問題だったのか

Data は untyped なメモリの連続領域を表すコンテナ型です。DataRangeReplaceableCollection に適合しているため、Sequence<UInt8> を介してバイト列を操作する多くの API を継承しています。

一方で、ライブラリの世界では性能特性とライフタイム保証を目的として、連続メモリを表すために SpanRawSpan といったライフタイムに束縛された span 型を提供する API が増え始めています。span を受け渡しする API が広がるにつれ、Data からもそれらと直接やり取りできることが重要になってきました。Data はすでに初期化済みの領域へアクセスするための bytes プロパティを備えていますが、span を通じて Data を初期化したり書き換えたりするための API は用意されていませんでした。

さらに、これまでに承認されていた span ベースの初期化・追加 API は OutputSpan<UInt8> を使い、命名も個別に決められた状態でした。しかし SE-0527 が承認され、UniqueArray のような span ベースで RangeReplaceableCollection に相当する API の命名規約が固まったため、Data の span ベース API もこの規約に合わせて整理し直す必要が出てきました。

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

UniqueArraySE-0527)で確立された命名規約に沿って、Data に span ベースの初期化・変更 API を追加します。ただし要素型を持つ Span<Element> ではなく RawSpan を使います。これは、Data が「untyped なメモリの連続領域」という意味づけを持つためで、RawSpan の方がその性質によく合うからです。要素型付きの Span<T> としてバイト列を扱いたい場合は、SE-0525 の API を使って RawSpanSpan<T>OutputRawSpanOutputSpan<T> を相互に変換できます。

追加される API

RawSpan から Data を作ったり、末尾へ追加したりする操作は、次のように書けます。

var someBytes: RawSpan = /* span を提供する API からバイト列を受け取る */
var data = Data(copying: someBytes)
data.append(copying: someBytes)

Data の内部ストレージを直接編集したい場合は、OutputRawSpan を受け取るクロージャを使います。edit(_:) はクロージャを最大1回だけ呼び出し、その中でバイトの追加・削除・並べ替えを自由に行えます(ただし span 自体の差し替えや capacity の変更はできません)。クロージャが終了すると、DataOutputRawSpan の最終的な内容に更新されます。

data.edit { outputRawSpan in
    // outputRawSpan を通じて Data の中身を直接書き換える
}

指定位置へバイトを挿入する API では、挿入するバイト数と位置を渡し、クロージャで未初期化のストレージを直接埋められます。

var prefix: RawSpan = /* バイト列 11, 99 を持つ RawSpan */
var buffer = Data(capacity: 20, copying: prefix)
var i: UInt8 = 0
buffer.insert(addingCount: 3, at: 1) { target in
    while !target.isFull {
        target.append(i)
        i += 1
    }
}
// buffer は 11, 0, 1, 2, 99 というバイト列になる

RawSpan をそのままコピーして挿入する insert(copying:at:) や、範囲を置き換える replaceSubrange(_:addingCount:initializingWith:) / replaceSubrange(_:copying:) も同じ考え方で用意されます。クロージャを使う挿入・置換では、クロージャが OutputRawSpan を最後まで埋めなかったり途中でエラーを throw したりした場合でも、それまでに初期化できたバイトは保持されます。これらの API は要素型を持つコレクションの操作と対応する initializer を使い、throws(E) の typed throws にも対応しています。

追加される主な API は次のとおりです。

extension Data {
    public init(capacity: Int? = nil, copying span: RawSpan)

    public mutating func edit<E: Error, R: ~Copyable>(
        _ body: (inout OutputRawSpan) throws(E) -> R
    ) throws(E) -> R

    public mutating func append(copying newBytes: RawSpan)

    public mutating func insert<E: Error>(
        addingCount newBytesCount: Int,
        at index: Int,
        initializingWith initializer: (inout OutputRawSpan) throws(E) -> Void
    ) throws(E)

    public mutating func insert(copying newBytes: RawSpan, at index: Int)

    public mutating func replaceSubrange<E: Error>(
        _ subrange: Range<Int>,
        addingCount newBytesCount: Int,
        initializingWith initializer: (inout OutputRawSpan) throws(E) -> Void
    ) throws(E) -> Void

    public mutating func replaceSubrange(_ subrange: Range<Int>, copying newBytes: RawSpan)
}

既存 API の修正

これまでに承認されていた span ベースの初期化・追加 API も、RawSpan / OutputRawSpan に揃える形で整理します。

  • Data(rawCapacity:initializingWith:)Data.append(addingRawCapacity:initializingWith:) は削除します。
  • Data(capacity:initializingWith:)Data.append(addingCapacity:initializingWith:) は、クロージャの引数を OutputSpan<UInt8> から OutputRawSpan に変更します。あわせて後者は SE-0527 の命名規約に従い Data.append(addingCount:initializingWith:) に改名します。
extension Data {
    public init<E: Error>(
        capacity: Int,
        initializingWith initializer: (inout OutputRawSpan) throws(E) -> Void
    ) throws(E)

    public mutating func append<E: Error>(
        addingCount newByteCount: Int,
        initializingWith initializer: (inout OutputRawSpan) throws(E) -> Void
    ) throws(E)
}

これにより、Data の span ベース API は raw な span 型に一本化されます。untyped 版と UInt8 版を二重に用意する必要がなくなり、Data が「バイト列を untyped なメモリとして扱う」という約束を素直に表現できます。これらの修正対象 API は承認済みですが正式にはまだ出荷されていないため、安定した利用者に影響を与えずに変更できます。

なお、これらの API のavailabilityは span 型のavailabilityに揃えられ、span 型が backdeploy されている範囲まで遡って利用できます。