1. Значення іноді відсутнє
Почнімо не із синтаксису, а з життя. У програмуванні постійно трапляються ситуації, коли значення обʼєктивно може бути відсутнім, і це не помилка програміста, а нормальна реальність. Користувач міг нічого не ввести, рядок може виявитися не числом, пошук може нічого не знайти, файл може не існувати — і все це не «аварія», а один із можливих сценаріїв.
У деяких мовах цю проблему історично вирішували «магією»: наприклад, повертали null (або nil), ніби це звичайне значення. А потім застосунок падав у найнесподіванішому місці. Swift вирішив, що такий сюрприз — сумнівний подарунок, і зробив відсутність значення частиною типу. Тобто «може не бути» — це не натяк, а контракт.
Optional: не «значення зі знаком питання», а модель даних
Тепер формально і по суті. У Swift запис T? означає: «це або значення типу T, або nil».
- String означає: «рядок точно є».
- String? означає: «рядок може бути, а може й бути відсутнім».
І ось тут важлива думка, яку треба справді прийняти: String і String? — різні типи. Не «майже те саме», не «ну рядок же», а саме різні типи.
Уявіть коробку: String — це рядок без коробки, ви тримаєте його в руках. А String? — це коробка, усередині якої або лежить рядок, або лежить записка «порожньо». І Swift не дозволяє вдавати, що коробки немає: спершу відкрийте її, подивіться, що всередині, і лише потім використовуйте.
Мініприклад:
let name: String = "Alice"
let maybeName: String? = "Bob"
let noName: String? = nil
print(name) // Alice
print(maybeName) // Optional("Bob")
print(noName) // nil
Зверніть увагу на Optional("Bob"). Swift буквально показує вам: «це не рядок, це опціональний рядок».
2. Перше знайомство з Any та as
Перш ніж іти далі, варто ввести ще два слова, які незабаром почнуть зʼявлятися в прикладах: Any і as.
У Swift є спеціальний тип Any. Він означає: «значення будь-якого типу». Офіційна документація Swift прямо каже, що Any може представляти значення будь-якого типу, включно з опціональними значеннями.
Наприклад:
let a: Any = 42
let b: Any = "hello"
let c: Any = true
В усіх трьох випадках змінні мають тип Any, хоча всередині лежать зовсім різні значення.
Тепер про as. Це оператор, який у загальному сенсі можна читати як «розглядай це значення як інший тип». У цій лекції нам не потрібна вся велика тема приведення типів. Нам достатньо вузької й практичної ідеї: запис
value as Any
означає: «передай це значення як Any».
Саме тому в прикладах нижче ми іноді будемо писати так:
let line: String? = readLine()
print(line as Any)
Навіщо це потрібно? Тому що нам хочеться чесно побачити, що саме лежить у змінній: справжнє значення чи nil. Оскільки Any уміє зберігати і звичайні значення, і опціональні значення, запис as Any тут буквально означає: «покажи це як є, нічого не приховуючи».
Тому поки що можна запамʼятати дуже просту модель:
- Any — це тип «для будь-якого значення».
- as — це спосіб явно сказати Swift, у якому типі ми хочемо зараз бачити значення.
- as Any у цій лекції потрібен здебільшого для виведення та налагодження, щоб зручніше бачити Optional("...") або nil.
Пізніше ми ще повернемося до Any і as. Зараз нам важливо лише одне: вони допомагають чітко побачити, що Optional — це не «майже звичайне значення», а окремий тип, усередині якого може бути або значення, або nil.
3. Звідки взагалі беруться Optional у коді
Важливо відчути, що Optional — не штучна складність мови, а відображення реальності. Почнімо з прикладів, які ви вже бачили.
Ввід через readLine() — це String?
readLine() повертає String?, тому що є ситуація, коли рядок прочитати неможливо: наприклад, введення завершилося (EOF). Тобто «рядка немає» — цілком реальний сценарій.
let line: String? = readLine()
print(line as Any) // наприклад: Optional("hello") або nil
Чому as Any? Тому що нам зараз треба побачити саму форму значення, без домислів: nil це чи не nil.
Парсинг Int(text) — це Int?
Int("42") працює, а Int("сорок два") — ні. І «ні» тут виражається як nil.
let good = "42"
let bad = "сорок два"
let a: Int? = Int(good)
let b: Int? = Int(bad)
print(a as Any) // Optional(42)
print(b as Any) // nil
І це цілком логічно: рядок може бути числом, а може й не бути ним.
Чому це зручно, а не шкідливо
Якщо мова змушує вас обробити ситуацію, коли значення може бути відсутнім, ви пишете код, який:
- менше падає;
- легше читати, бо сценарії явно прописані;
- простіше налагоджувати, бо nil не «ховається».
4. nil: не «порожнє значення», а окремий стан
Тут новачки часто роблять одну й ту саму логічну помилку: плутають «немає значення» і «значення є, але воно порожнє або нульове».
Порівняймо:
- "" — рядок є, але він порожній (наприклад, користувач натиснув Enter).
- 0 — число є, але це нуль; а нуль цілком може бути коректним значенням.
- nil — значення немає взагалі.
Покажімо це кодом:
let emptyText: String = ""
let maybeText: String? = nil
print(emptyText.isEmpty) // true
print(maybeText == nil) // true
print(maybeText ?? "<порожньо>") // <порожньо>
Бачите різницю? "" — це «мені дали рядок, але він порожній», а nil — це «мені нічого не дали».
5. Чому не можна використовувати T? як T
Зараз буде момент, коли Swift виглядає як охоронець у клубі: «З типом-опціоналом не пущу». Але насправді він робить вам послугу.
Уявімо функцію:
func greet(_ name: String) {
print("Привіт, \(name)!")
}
І в нас є такий ввід:
let maybeName: String? = readLine()
Логіка новачка часто така: «Ну maybeName ж майже рядок. Давайте викличемо greet(maybeName)».
І ось тут компілятор скаже: «Ні, бо ви можете спробувати привітати nil».
Сенс заборони дуже простий: функція обіцяла отримати справжній рядок, а ви намагаєтеся передати коробку, яка може виявитися порожньою.
Це ключова ідея сьогоднішнього дня: Swift змушує вас явно обрати поведінку для випадку nil. І саме тут мова не ускладнює життя, а не дає вам написати застосунок, який упаде «колись потім».
6. Таблиця: T проти T?
Щоб не тримати все це як абстракцію, давайте зафіксуємо відмінності у вигляді таблиці.
| Річ | |
|
|---|---|---|
Може бути |
Ні | Так |
| Чи потрібно обробляти відсутність значення | Ні | Так, обовʼязково |
Чи можна передати туди, де очікують |
Так | Ні |
| Типове походження | «точно є» (літерал, обчислення, гарантована логіка) | ввід, парсинг, пошук, дані ззовні |
| Головна ідея | «значення гарантовано» | «значення або є, або відсутнє» |
7. Мінізастосунок: заготовка LibraryCLI
Відтепер ми почнемо збирати невеликий консольний застосунок у стилі «CLI» (командного рядка). Поки що він буде простим: ми просто читаємо рядки-команди й друкуємо, що отримали. Жодних структур і «справжньої» моделі предметної області — це буде пізніше. Зараз нам важливіша дисципліна: ввід — це Optional.
Зробімо цикл читання рядків. Поки що без гарного розбору команд — лише «є рядок / немає рядка».
import Foundation
print("Введіть команду (або Ctrl+D для завершення):")
while true {
let line: String? = readLine()
if line == nil {
print("Введення завершилося, закінчуємо роботу.")
break
}
let command = line ?? ""
print("Команду отримано: \(command)")
}
Так, тут є line ?? "", і це справді нагадує тимчасовий обхідний шлях. І це нормально для першого кроку: ми ще не вміємо красиво витягувати значення. Головна мета прикладу — показати, що застосунок не падає, а коректно завершує роботу, якщо введення вже немає.
8. Optional у логіці застосунку: сценарії та інструменти
Розгалуження сценаріїв
Коли у вас зʼявляється Optional, далі майже завжди логіка одна й та сама: потрібно обрати один зі сценаріїв.
Її зручно уявляти як маленьке розгалуження:
flowchart TD
A["Отримали значення типу T?"] --> B{Це nil?}
B -->|Так| C["Сценарій «значення відсутнє»"]
B -->|Ні| D["Сценарій «значення є»"]
Тобто Optional — це не «складний тип», а просто явно позначене розгалуження.
Стратегії роботи з Optional
Зараз ми зробимо важливу річ: перелічимо способи, якими Swift пропонує справлятися з Optional. Але без деталей синтаксису — деталі будуть у наступних лекціях. Тут нам важливо побачити картину цілком, як мапу місцевості.
- Перший спосіб — розгалуження: якщо значення є, робимо одне; якщо ні — інше. Це буде if let.
- Другий спосіб — ранній вихід: якщо чогось немає, виходимо, інакше продовжуємо основний код. Це буде guard let.
- Третій спосіб — значення за замовчуванням: якщо nil, підставляється значення за замовчуванням. Це оператор ??.
- Четвертий спосіб — безпечний доступ до властивостей і методів, коли обʼєкт може бути відсутнім. Це optional chaining ?.. При цьому важливо памʼятати правило: вираз із ?. завжди повертає Optional, навіть якщо праворуч «звичайний» тип. Цю ідею зручно тримати в голові так: nil має вміти «провалюватися» крізь увесь ланцюжок обчислень, не ламаючи застосунок.
- Пʼятий спосіб — примусове розгортання !. Воно виглядає спокусливо («компіляторе, відчепись»), але потребує залізної гарантії, інакше буде падіння. Тому ми ставитимемося до ! як до рідкісного інструменту, а не як до кнопки «зроби, щоб працювало».
Сьогодні ми дійдемо до всіх цих інструментів, але спершу розберімося, чому Optional взагалі існує.
Чому Optional — сильна сторона Swift
Іноді здається: «От би Swift дозволяв не думати про nil». Але це приблизно як мріяти: «От би дорожні знаки не заважали їздити». Так, формально було б швидше… до першого стовпа.
Optional у Swift дає вам три дуже практичні переваги.
Перша перевага — код стає чесним. Якщо дані можуть бути відсутніми, це видно за типом. Ви читаєте сигнатуру функції й одразу розумієте: «ага, тут може бути nil».
Друга перевага — помилки виявляються на етапі компіляції. Тобто не «користувач через тиждень надіслав звіт про помилку», а «IDE прямо зараз підсвітила місце, де ви забули обробити відсутність значення».
Третя перевага — ви проєктуєте логіку осмислено. Вам доводиться поставити запитання: «а що робити, якщо значення немає?» І це майже завжди покращує застосунок, бо робить поведінку передбачуваною.
Мікровправа: nil — це сценарій, а не виняток
Щоб закріпити ідею, давайте подумаємо про ввід команди для нашого майбутнього LibraryCLI.
Якщо користувач натиснув Ctrl+D і введення завершилося, то readLine() поверне nil. Це не «помилка Swift», а сигнал: «діалог завершено». І ваш код має обрати поведінку: завершитися.
Якщо користувач увів порожній рядок, це не nil. Це "". І поведінка може бути іншою: наприклад, «пропустити» введення й попросити ще раз. Тобто nil і "" справді ведуть до різних гілок логіки застосунку.
Саме для таких відмінностей Optional і існує.
9. Типові помилки під час знайомства з Optional
Помилка №1: плутати nil і «порожнє значення».
Часто будують логіку так, ніби nil, "" і "0" — це одне й те саме «нічого». Насправді це різні стани, і вони майже завжди мають вести до різної поведінки застосунку. Найпростіший спосіб не заплутатися — промовляти словами: «значення немає» проти «значення є, але воно порожнє».
Помилка №2: сприймати T? як «T, тільки незручно».
Якщо у вашій голові String? — це «рядок, але компілятор вередує», то далі зазвичай виникає бажання «перемогти компілятор». Правильна модель інша: String? — це «контейнер, який може бути порожнім», і ваше завдання — обробити обидва сценарії.
Помилка №3: тягнути Optional занадто далеко в коді.
Новачки часто отримують String? з readLine() і починають передавати його через функції, умови та цикли, а потім розгортати десь далеко, коли вже незрозуміло, що робити з nil. Стилістичне правило, яке ми використовуватимемо в курсі: краще розбиратися з Optional ближче до місця, де він зʼявився.
Помилка №4: підставляти значення за замовчуванням автоматично, аби тільки отримати T.
Оператор ?? дуже зручний, але якщо ви машинально пишете ?? "" або ?? 0, то можете непомітно зламати сенс. Іноді відсутність значення — це сигнал «завершіть застосунок» або «попросіть повторити введення», а не «підставте нуль і робіть вигляд, що все нормально».
Помилка №5: «полагодити» компіляцію через !.
Примусове розгортання допомагає швидко скомпілювати код, але підвищує ризик падіння в середовищі виконання, якщо nil усе-таки трапиться. У нашому курсі ! розглядатиметься як рідкісний інструмент із чіткими критеріями, а не як кнопка «зроби, щоб працювало».
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ