Swift Digest
Blog | Swift.org Blog

Proposalの舞台裏 — 生テキストをサポートするため String Literals の区切りを強化する SE-0200

Behind the Proposal — SE-0200 Enhancing String Literals Delimiters to Support Raw Text

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

この記事の要点

背景: エスケープシーケンスと raw string

エスケープシーケンスは、\\\"\u{n} のようにバックスラッシュを前置して、通常の文字列リテラル内では表現しづらい文字を表す仕組みです。Swift では \0(ヌル文字)、\\(バックスラッシュ)、\t(水平タブ)、\n(改行)、\r(復帰)、\"\' といった特殊文字、\u{n} 形式の Unicode スカラ、そして \() で囲む補間式が使えます。たとえば "hello\n\n\tworld" は、1 行目に “hello”、3 行目にタブで字下げされた “world” を持つ 3 行の文字列になります。

一方で raw string は、エスケープシーケンスを解釈せず内容をすべて文字どおりに扱います。raw string では \n は「改行」ではなく「バックスラッシュと文字 n」を表します。これは正規表現、コードを生成・表示するアプリ、アプリ内ソースコード、JSON や XML のようにあらかじめエスケープされたコンテンツを扱う場面で役立ちます。

raw string は C#、Perl、Rust、Python、Ruby、Scala など多くの言語にあり、区切り方は言語ごとに異なります。多くは qRr などの接頭辞で raw を示しますが、Rust と Java はさらにカスタマイズ可能な区切りを許し、区切り文字そのものを文字列内に含められるようにしています。

複数行文字列リテラルが示した方向性

SE-0168: Multi-Line String Literals は、改行のエスケープなしで複数行の文字列リテラルを書く手段を導入しただけでなく、Swift がカスタムの区切りへ向かう方向を先取りしていました。複数行文字列は """ で開始・終了するため、個々の引用符や改行をエスケープなしで書けます。たとえば次のようなエスケープだらけのリテラルが、

"\"Either it brings tears to their eyes, or else -\"\n\n\"Or else what?\" said Alice..."

""" で囲むことで、引用符と改行のエスケープが消えて読みやすくなります。

"""
    "Either it brings tears to their eyes, or else -"

    "Or else what?" said Alice...
    """

複数行文字列は補間・Unicode 挿入などの機能をすべて保ったままです。この体験が、Swift の「raw」文字列がどうあるべきかの基準になりました。

設計が固まるまで

SE-0200 は 2018 年 3 月に最初のレビューに入りました。当初の設計は単純に r 接頭辞を付けるものでしたが、「r"..." という構文が言語の他の部分になじまない」「ユースケースを十分にカバーできていない」といった理由でコミュニティの支持を得られず、4 月に差し戻されました。

設計を練り直す中で着目したのが Rust です。Rust は raw string にカスタマイズ可能な区切りを使い、r#""#r##""## のように # の数で区切りを調整できます。文字列内に区切りと衝突する内容が含まれても、# を増やせば終端を調整できる仕組みです。SE-0200 は改訂で r を落とし、この Rust 流の # を両端に付ける設計を採用しました。

ここで「カスタム区切りは単なる raw string 以上の力を持つ」という気づきが生まれます。raw string は定義上エスケープを使わないため、補間が使えなくなります。そこで co-author の Brent Royal-Gordon が、Rust 由来の構文を取り入れつつエスケープへのアクセスを残すというアイデアを示しました。これにより SE-0200 は、raw string の力と Swift の補間の便利さを両立する設計になりました。

どう動くのか: # でエスケープも変わる

SE-0200 は、文字列リテラルの両端にカスタム区切りを追加すると同時に、エスケープトークンを単なるバックスラッシュから # 付きのものに変えます。"" 文字列ではエスケープは \#""# では \###""## では \## です。エスケープがこの # 付きトークンに一致したときだけ、エスケープとして解釈されます。

"\(thisInterpolates)"
#"\(thisDoesntInterpolate) \#(thisInterpolates)"#
##"\(thisDoesntInterpolate) \#(thisDoesntInterpolate) \##(thisInterpolates)"##

"\n" // 改行
#"\n"# // バックスラッシュと n
#"\#n"# // 改行

エスケープと解釈されてほしくない内容が出てきたら、内容が解釈されなくなるまで区切りの # を増やせます。この機能が必要になることはまれですが、# を 1 つか 2 つ付けるだけで、文字列の一部では補間を有効に、別の部分では無効にできます。

コードでの使い方

Swift 5 では、次のリテラルはいずれも文字列 “Hello” を表します。

let u = "Hello"           // # なし
let v = #"Hello"#         // # ひとつ
let w = ####"Hello"####   // # 多数
let x = "\("Hello")"      // 補間
let y = #"\#("Hello")"#   // # 付きの補間
let z = """               // 複数行
    Hello
    """
let a = #"""              // # 付きの複数行
    Hello
    """#

使い方のルールは次のとおりです。

これにより、エスケープ済みの JSON を含むネットワークコードや、バックスラッシュの多いコンテンツ、コード生成ツールの出力などを、補間やエスケープの便利さを犠牲にせず、そのまま貼り付けて使えるようになります。エスケープのごたごたが減り、読みやすく、コピー&ペーストしやすいコードになります。

関連リンク