Swift Digest
SE-0182 | Swift Evolution

String Newline Escaping

Proposal
SE-0182
Authors
John Holdsworth, David Hart, Adrian Zubarev
Review Manager
Chris Lattner
Status
Implemented (Swift 4.0)

01 何が問題だったのか

SE-0168 で導入された複数行の文字列リテラル(""" で囲む形式)では、ソース中の改行がそのまま文字列リテラルの値に含まれる改行として扱われます。このため、非常に長い一続きのテキストを文字列リテラルとして書きたいとき、

  • 改行を入れずに一行でそのまま書く(横スクロールが必要になり読みにくい)
  • 改行を入れて複数行に分ける(本来入れたくない改行が文字列の値に混ざってしまう)

の二択しかなく、「ソース上は複数行に折り返したいけれど、文字列の値としては一行のまま扱いたい」 という書き方ができませんでした。

たとえば次のような長いテキストをリテラルで書く場合、一行で書くと読みづらく、安易に改行を入れると余計な改行が値に入り込んでしまいます。

// 一行で書くと画面外にはみ出して読みづらい
let text = """
    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
    """

もともと SE-0168 の議論の中で「行末の \ で改行をエスケープしたい」というアイデアは出ていましたが、当時は複数行リテラル専用の機能にすべきか、通常の文字列リテラルにも入れるべきかを含めて結論が出ず、追加機能として後回しになっていました。SE-0182 はその保留されていた課題に正面から取り組むものです。

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

文字列リテラルに 行継続文字(line continuation character) として \ が導入されます。行末を \ で終えると、その \ と直後の改行は文字列の値に含まれなくなり、ソース上の見た目だけを複数行に分けつつ、値としては一行のまま書けるようになります。

基本的な使い方

複数行リテラル(""")でも通常の文字列リテラル(")でも同じように使えます。

// 複数行リテラル
let text = """
    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \
    tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, \
    quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
    """

上のコードは、\ で終わっている行の改行が値から取り除かれるため、結果として改行を一つも含まない長い一行の文字列になります。

詳細なルール

行継続として認識されるのは、正規表現で書くと \\[ \t]*\n にあたるパターンです。つまり、

  • \ のあとに続く 水平方向の空白文字(スペースやタブ)は無視 される
  • \ より にある空白文字は、そのまま文字列の値に含まれる
  • リテラルの 最終行の末尾\ を書くのはエラー(続く改行が無いため)

という挙動になります。\ と改行の間にうっかり半角スペースが入ってしまっても行継続として機能するため、エディタの自動整形などで末尾の空白が残っても問題になりません。

具体例として、複数行リテラルと通常の文字列リテラルの両方で同じ値が得られることが次のように確認できます( は実際の改行を表しています)。

let str1 = """↵
    line one \↵
    line two \          ↵
    line three↵
    """

let str2 = "line one \↵
line two \          ↵
line three"

assert(str1 == "line one line two line three")
assert(str2 == "line one line two line three")
assert(str1 == str2)

複数行リテラルのインデント除去との関係

複数行リテラルでは、閉じる """ の位置を基準に各行の先頭のインデントが取り除かれる機能があります(SE-0168)。行継続の \ はこのインデント除去の挙動には影響しません。インデント除去は従来どおり機能したうえで、行の末尾の \ が残った行の改行だけを追加で取り除く、という順序で考えると素直です。

また、通常の文字列リテラルにはインデント除去の機能はありません。そのため、行継続を使って通常の文字列リテラルを複数行に分けると、2 行目以降の先頭のインデントはそのまま値に含まれてしまう点に注意が必要です。インデントごときれいに取り除きたいときは、複数行リテラルを使うほうが素直です。