Strings and Characters

This section documents the Swift 6 language mode as shipped by Swift 6.3, as published in The Swift Programming Language at docs.swift.org, which is the reference these pages are written and verified against. 6.4-beta-only features are always flagged as such — never presented as baseline.

This content was generated with the assistance of AI and should be verified against docs.swift.org before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

String is a value type built on Unicode from the ground up: what looks like "one character" to a reader is, internally, a cluster of one or more Unicode scalars, and almost every quirk in this page follows from taking that seriously instead of papering over it with a fixed-width code unit.

String Literals

let quotation = "Even the all-powerful Pantagruel was affected by his mother's death."

let quotationWithEscapes = "Imagination is more \"important\" than knowledge.\n\tAlbert Einstein"

let threeDoubleQuotationMarks = """
Escaping the first quotation mark \"""
Escaping all three quotation marks \"\"\"
"""

// Multiline strings: opening/closing """ each on their own line; the closing """'s
// leading whitespace sets the indentation stripped from every line of content.
let softWrappedQuotation = """
    The White Rabbit put on his spectacles.  "Where shall I begin,
    please your Majesty?" he asked.

    "Begin at the beginning," the King said gravely, "and go on
    till you come to the end; then stop."
    """

// A single trailing backslash at the end of a line joins it to the next, without a newline.
let singleLineFromMultipleLines = """
    This string starts with a line break, then has a second line, \
    joined to the third.
    """

// Extended delimiters (`#"..."#`): backslash escapes and interpolation are disabled by
// default, so backslashes and quotes need no escaping -- add matching `#`s for interpolation.
let noEscaping = #"Line 1 \n Line 2"#                          // the \n is literal text, not a newline
let stillInterpolates = #"6 times 7 is \#(6 * 7)."#            // "6 times 7 is 42."
let alsoNoEscaping = ###"Line1\###nLine2"###                    // more #s if the content itself contains #"

Mutability and Value Semantics

Strings and characters use let/var exactly like every other Swift value — there is no separate mutable string builder type for ordinary use:

var variableString = "Horse"
variableString += " and carriage"     // fine -- variableString is a var

let constantString = "Highlander"
// constantString += " and another Highlander"   // error: constantString is a let

Assigning a String to a new constant or variable, or passing it to a function, copies it, in keeping with Swift’s value semantics for structs — see Structures and Classes. Swift’s implementation makes this efficient in practice: a copy is only actually made — copy-on-write — the moment either the original or the copy is mutated, not at the point of assignment.

Character

A Character represents a single extended grapheme cluster — one or more Unicode scalars that together produce a single human-perceived glyph:

for character in "Dog!🐶" {
    print(character)          // D, o, g, !, 🐶 -- five iterations, five Characters
}

let exclamationMark: Character = "!"
let catCharacters: [Character] = ["C", "a", "t", "!", "🐱"]
let catString = String(catCharacters)     // "Cat!🐱" -- build a String back up from Characters

Concatenation and Interpolation

let string1 = "hello"
let string2 = " there"
var welcome = string1 + string2          // "hello there" -- + concatenates two Strings
welcome += "!"                            // += appends in place

var instruction = "look over"
instruction += exclamationMark            // a String can also append a single Character

let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"   // interpolation embeds any expression

Unicode Scalars vs. Extended Grapheme Clusters

A Unicode scalar is a single 21-bit Unicode code point (U+0000…​U+D7FF or U+E000…​U+10FFFF); an extended grapheme cluster is a sequence of one or more scalars that together produce what a reader perceives as one character. String, Character and count are all defined in terms of grapheme clusters, not scalars:

let eAcute: Character = "\u{E9}"                       // é, a single scalar
let combinedEAcute: Character = "\u{65}\u{301}"        // e + ́  (COMBINING ACUTE ACCENT) -- STILL one Character
print(eAcute == combinedEAcute)                         // true -- canonically equivalent grapheme clusters

let precomposed = "\u{D55C}"                             // 한, precomposed
let decomposed = "\u{1112}\u{1161}\u{11AB}"              // ᄒ+ᅡ+ᆫ, three scalars -- STILL one Character/한
print(precomposed == decomposed)                         // true

let enclosedEAcute: Character = "\u{E9}\u{20DD}"         // é + combining enclosing circle -- one Character: é⃝
print("\(eAcute) has \(String(eAcute.unicodeScalars.count)) scalar(s); "
    + "\(enclosedEAcute) has \(String(enclosedEAcute.unicodeScalars.count)) scalar(s)")

This is why "café".count is 4 even when "é" is stored as two scalars, and why counting characters walks the whole string rather than reading a stored length — see the figure below.

A Swift string as a sequence of extended grapheme clusters, each one built from one or more Unicode scalars, each scalar in turn stored as one to four UTF-8 code units, with String.Index positions marked between grapheme clusters rather than between code units

String.Index and Why There Is No Integer Indexing

Because a Character can occupy a variable number of bytes, String has no subscript(Int) — indexing by an arbitrary integer would be either wrong (if it meant "the nth UTF-8 byte", possibly slicing through a multi-byte scalar) or O(n) every time (if it meant "the nth grapheme cluster"), so Swift makes that cost visible instead of hiding it behind []:

let greeting = "Guten Tag!"

print(greeting[greeting.startIndex])                          // "G"
print(greeting[greeting.index(before: greeting.endIndex)])    // "!"
print(greeting[greeting.index(after: greeting.startIndex)])   // "u"

let index = greeting.index(greeting.startIndex, offsetBy: 7)
print(greeting[index])                                          // "a"

// greeting[greeting.endIndex]                    // runtime error: endIndex is one-past-the-end
// greeting.index(after: greeting.endIndex)        // runtime error: nothing after the end

for index in greeting.indices {
    print(greeting[index], terminator: "")           // walks every grapheme cluster, in order
}

Inserting and Removing

var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)                          // "hello!"
welcome.insert(contentsOf: " there", at: welcome.index(before: welcome.endIndex))   // "hello there!"

welcome.remove(at: welcome.index(before: welcome.endIndex))         // removes "!" -> "hello there"
let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex
welcome.removeSubrange(range)                                        // "hello"

Substring and Slicing

Subscripting a String with a Range<String.Index> yields a Substring, a value that shares the original string’s storage instead of copying it — meant only for short-term use, since holding one keeps the whole original string’s storage alive:

let greeting = "Hello, world!"
let index = greeting.firstIndex(of: ",") ?? greeting.endIndex
let beginning = greeting[..<index]        // "Hello" -- a Substring, no copy made yet

let newString = String(beginning)          // convert to a String once you plan to keep it around

Comparison and Prefix/Suffix Equality

let quotation = "We're a lot alike, you and I."
let sameQuotation = "We're a lot alike, you and I."
print(quotation == sameQuotation)                     // true -- Unicode canonical equivalence, not byte equality

let romeoAndJuliet = [
    "Act 1 Scene 1: Verona, A public place",
    "Act 1 Scene 2: Capulet's mansion",
    "Act 2 Scene 1: Outside Capulet's house",
]
let act1SceneCount = romeoAndJuliet.filter { $0.hasPrefix("Act 1 ") }.count      // 2
let mansionCount = romeoAndJuliet.filter { $0.hasSuffix("mansion") }.count       // 1

== compares strings for Unicode canonical equivalence: two strings are equal if their extended grapheme clusters are canonically equivalent, even when composed from a different number of underlying scalars, exactly as shown for eAcute/combinedEAcute above.

The UTF-8, UTF-16, and Unicode Scalar Views, and Common StringProtocol Operations

Every String exposes three alternative views over the same underlying data, each a Collection in its own right:

let dogString = "Dog‼🐶"

for codeUnit in dogString.utf8 { print(codeUnit, terminator: " ") }
print()   // 68 111 103 226 128 188 240 159 144 182 -- UTF-8 code units

for codeUnit in dogString.utf16 { print(codeUnit, terminator: " ") }
print()   // 68 111 103 8252 55357 56374 -- UTF-16 code units (the "🐶" needs a surrogate pair)

for scalar in dogString.unicodeScalars { print(scalar.value, terminator: " ") }
print()   // 68 111 103 8252 128054 -- Unicode scalars

// Common StringProtocol operations, shared by both String and Substring:
print(dogString.isEmpty, dogString.count, dogString.uppercased(), dogString.lowercased())
print(dogString.contains("🐶"), dogString.replacingOccurrences(of: "Dog", with: "Cat"))
print("  padded  ".trimmingCharacters(in: .whitespaces))

StringProtocol is the shared protocol behind both String and Substring, which is why a function parameter typed some StringProtocol (or String, accepting a Substring implicitly converted at the call) works with either — see Standard Library Overview for where String sits among the other collection types.

See Also

  • Collections — Array, Set and Dictionary, the other standard-library value types String shares its Collection conformance style with.

  • Lexical Structure and Style — every string-literal token form, at the lexical level.

  • Regular Expressions — pattern matching over strings with Regex and regex literals.

  • Functional Programming — map/filter/ reduce as used above over a string’s characters.