Strings in Swift: Why substring doesn't work the way you're used to?

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: The "Gotcha" That Awaits Every Swift Developer

\n\n

Imagine this: you're confidently writing code in Swift, everything is going smoothly, and then you need to take the first three characters from a string. Without thinking, you write something like myString[0..<3] — and you get... a compilation error. Sound familiar?

\n\n

Welcome to the world of String substring in Swift. This is one of those topics that causes real pain for beginners and even experienced developers transitioning from other languages. The question "How does String substring work in Swift" on Stack Overflow has gathered thousands of views — and for good reason.

\n\n

In this article, we'll break down why Swift is so "strange" when working with substrings, how to correctly use substring in Swift, and what new features have appeared in recent versions. Let's go!

\n\n

Why Doesn't Swift Just Let You Cut a Piece of a String?

\n\n

In most languages (Python, JavaScript, C#), a string is simply an array of characters. Want a substring? Write str[0:3] and get the result. In Swift, it's different.

\n\n

The reason lies in Unicode handling. Strings in Swift are not just bytes or ASCII characters. They consist of grapheme clusters. A single character visible to the user (e.g., "é" or "👍🏽") can occupy multiple Character or even multiple Unicode.Scalar values. Imagine cutting a string in the middle of such a cluster — you'd get corrupted data.

\n\n

Therefore, Swift uses indices (String.Index) rather than integers. This ensures you never split a character in half.

\n\n

Basics: String.Index and Simple Substring Extraction

\n\n

Let's start with a basic example. Suppose we have a string:

\n\n
let fullString = "Привет, Swift!"\n// We want to take the word "Swift"\nlet startIndex = fullString.index(fullString.startIndex, offsetBy: 8)\nlet endIndex = fullString.index(fullString.startIndex, offsetBy: 13)\nlet substring = fullString[startIndex..<endIndex]\nprint(substring) // Prints: "Swift"
\n\n

Notice: substring has the type Substring, not String. This is an important distinction. Substring is a "window" into the original string. It doesn't copy data, but simply references a portion of the original string's memory. This is super efficient, but can lead to memory leaks if the original string is large and the substring is small.

\n\n

To convert a Substring into a full String, simply use the initializer:

\n\n
let newString = String(substring)\nprint(newString) // "Swift"
\n\n

Syntactic Sugar: RangeExpression

\n\n

Writing index(offsetBy:) every time is tedious. Fortunately, Swift allows shorter forms using RangeExpression.

\n\n

The ... Operator (ClosedRange)

\n
let str = "Hello, World!"\nif let range = str.range(of: "World") {\n    let worldSubstring = str[range]\n    print(worldSubstring) // "World"\n}
\n\n

Partial Ranges

\n

Want to take everything up to a certain index or after it? Easy:

\n
let text = "Swift Programming"\nlet prefix = text[..<text.index(text.startIndex, offsetBy: 5)]\nprint(prefix) // "Swift"\n\nlet suffix = text[text.index(text.startIndex, offsetBy: 6)...]\nprint(suffix) // "Programming"
\n\n

Note: prefix and suffix are also Substring.

\n\n

Built-in Methods: prefix, suffix, dropFirst, dropLast

\n\n

Swift provides convenient methods that save you from manually calculating indices. They return Substring.

\n\n