Swift 5 gives us Nice Things™: Custom string delimiters

Say, for whatever reason, you were in desperate need of Bill the Cat ASCII art in your app. Maybe you were very drunk and had a bet. Maybe you were working with some kind of Unix awk client. I dunno. Let’s just start with the proposition that this was a real and meaningful challenge in your life.

In Swift 4.2, you could use multiline triple-quoted strings. Multiline strings are great for preserving indentation and other formatting, plus as a bonus, you get embedded quotes for free. Let me show you an example of how this works.

Instead of:

"\"I don't think...\"\n\"Then you shouldn't talk,\" said the Hatter."

with its multiple escape sequences, Swift 4.x gave us:

"""
    "I don't think..."
    "Then you shouldn't talk," said the Hatter.
    """

Same results, less fuss. The triple-quote string delimiter replaces the one-quote, so you don’t have to use backslash escaping. Dialog flows freely in 4.x.

But what happens when your source uses backslashes, as does this Bill the Cat clip art?

// error: invalid escape sequence in literal
let billTheCat = """
<------- ____
  &&&    /    \  __ _____,
    `-- |  o   \'  `  &&/
       `|      | o  },-'
         \____( )__/
         ,'    \'   \
 /~~~~~~|.      |   .}~~~\
  ,-----( .     |   .}--.
        | .    /\___/
         `----^,\ \
                \_/
"""

Nearly every backslash triggers a compiler error as an invalid escape sequence. The others are subsumed into an unintentionally valid escape (like \'). You have inadvertent line escaping at the ends of the seventh and eighth lines as well.

You have to escape each backslash, losing the ability to evaluate your clip art source at a glance. Every escape pushes characters on that line one item to the right. Given that this is Bill the Cat, not much is lost aesthetically speaking, but the results are less maintainable and harder to visualize.

let billTheCat = """
<------- ____
  &&&    /    \\  __ _____,
    `-- |  o   \\'  `  &&/
       `|      | o  },-'
         \\____( )__/
         ,'    \\'   \\
 /~~~~~~|.      |   .}~~~\\
  ,-----( .     |   .}--.
        | .    /\\___/
         `----^,\\ \\
                \\_/
"""

The problem becomes more pronounced when you work with pre-escaped material instead of visual art. For example, you may have escaped JSON or XML you want to paste directly into an app’s string. Each stringity escape sequence is an error waiting to happen:

{\r\n  \"first_name\": \"John\",\r\n  \"last_name\": \"Smith\",\r\n  \"is_alive\": true,\r\n  \"age\": 27,\r\n  \"spouse\": null\r\n}

Enter Swift 5. Its custom delimiters work on escape sequences the way that multiline strings work on dialog. Just add a pound sign (#) to each end of your string and your backslashes are perfectly preserved without hand edits or audits:

// Perfect Bill
let billTheCat = #"""
<------- ____
  &&&    /    \  __ _____,
    `-- |  o   \'  `  &&/
       `|      | o  },-'
         \____( )__/
         ,'    \'   \
 /~~~~~~|.      |   .}~~~\
  ,-----( .     |   .}--.
        | .    /\___/
         `----^,\ \
                \_/
"""#

Those extra pounds allow you to change the way Swift interprets escape sequences. They transform escapes from simple backslashes to \#. To insert a newline into a pound-delimited string, you type \#n and not \n. Similarly, string interpolation becomes \#(...interpolation...).

This system was inspired by the Rust programming language. Rust stacks one or more pounds at each end of a string (and prefixes the letter “r”) to create what it calls “raw strings”, that is strings without further escape sequence interpretation.  You cannot incorporate interpolation or coded tabs, new lines, or returns.

Swift adopts the extensible delimiters (skipping the ugly “r”) but retains its useful escapes, including string interpolation. Swift adapts each escape sequence to match the number of pound signs used at the start and end of the string. Instead of “raw strings”, Swift has, well, let’s call them “medium rare strings”. It allows you to paste and preserve raw formatting while retaining the ability to insert escape sequences.

In Swift 5, each of the following declares "Hello", even though they use a variety of single and multiline styles:

let u = "Hello" // No pounds
let v = #"Hello"# // One pound
let w = ####"Hello"#### // Many pounds
let x = "\("Hello")" // Interpolation
let y = #"\#("Hello")"# // Interpolation with pound
let z = """ // Multiline
    Hello
    """
let a = #""" // Multiline with pound
    Hello
    """#

Here are the rules:

  • You must match the number of #-signs before and after the string, from zero to however many. “One” is almost always the right answer for “however many”.
  • When you use #-signs, you change the escape sequence from a single backslash to a backslash infixed with the same number of pound signs. A ##"Hello"##  string uses the \## escape sequence.
  • Anything that doesn’t match the closing delimiter is part of the string. To add """ to a multiline string without escaping, just change the delimiter with pound.
  • Use the least number of pound signs required for the results you need. Zero is best. One is fine. Two or more is very, very rare.

And that’s it. With this one small change, anyone writing code generation apps like PaintCode or Kite Compositor (or one of my many utilities), writing network code with escaped-JSON,  or including weird Bill the Cat clip art, can paste and go. Just add pounds as needed, without sacrificing the convenience of string interpolation or escape sequences.

SE-0200, which introduces this new behavior, is one of my favorite proposals I’ve ever worked on. I’ve been lucky enough to have had a working toolchain with these features (thanks John H!) since before the proposal was even adopted.  I can’t wait until Swift 5 debuts, allowing custom string delimiters to become part of everyone’s coding work as well.

4 Comments

  • Nice!!

  • `the # is as ugly as the ugly “#r” 😀

  • […] Erica Sadun: […]

  • hiii, Im trying to find a code for making calls for Xcode, but can’t find one that works correctly. Could you point me in the right direction or know a link for a phone call button for Xcode?

    Help!