JavaRush /Курси /Swift SELF /Безпечне вилучення значення

Безпечне вилучення значення

Swift SELF
Рівень 10 , Лекція 2
Відкрита

1. Навіщо потрібен if let

Коли ви вперше бачите String?, може здатися, ніби Swift спеціально ускладнює життя. В інших мовах ви б просто взяли рядок, а якщо його немає — «ну, якось потім розберемося» (найчастіше вже пізно вночі, у продакшені, з кавою та легкою панікою). Swift змушує зустрітися з проблемою раніше. Зате й простіше: компілятор не дає вам удавати, що значення точно є.

Суть Optional дуже проста: тип T? означає «або значення типу T, або відсутність значення (nil)». І головне: T? — це не те саме, що T. Тому, перш ніж працювати зі «звичайним» значенням, ми маємо передбачити поведінку на випадок nil. Найпряміший спосіб — зв’язування значень Optional через if let.

Базовий синтаксис if let

Якщо сказати простіше, if let читається так: «якщо в цій змінній справді лежить значення, тоді витягни його і дай мені у вигляді звичайного типу без ?». Тобто ми не просто перевіряємо, а створюємо нову змінну всередині блока, яка вже не містить ?.

Найкласичніша форма має такий вигляд:

let maybeAge: Int? = Int("21")

if let age = maybeAge {
    print("Вік: \(age)")        // Вік: 21
} else {
    print("Вік невідомий")
}

Зверніть увагу: всередині блока if змінна age має тип Int, а не Int?. Саме в цьому і полягає головна зручність if let: далі код читається спокійно, без танців навколо nil.

2. Область видимості й робота з Optional

Чому не можна використовувати String? як String

На цьому місці зазвичай трапляється діалог:

«Swift, ну я ж бачу, що там рядок!»
«А я — ні. І я не телепат.»

Подивімося на типову ситуацію:

let line: String? = readLine()
print(line.count) // ❌ так не можна

Компілятор не дасть вам викликати .count, тому що в String? немає властивості count. У нього є два стани: «є рядок» і «рядка немає». Коли рядка немає, .count просто не має сенсу.

Правильний шлях — витягнути значення:

let line: String? = readLine()

if let text = line {
    print(text.count)
} else {
    print("Введення відсутнє")
}

if let тут перетворює невизначену ситуацію («може бути рядок, може бути ні») на визначену: в одній гілці рядок точно є, в іншій — ми явно обробили відсутність.

Чому змінна з if let «зникає»

Спочатку це здається дивним: «Я ж щойно створив text, чому я не можу використовувати його далі?». Але тут усе логічно: змінна, створена в if let, живе лише всередині блока, тому що тільки там гарантовано існує значення.

Приклад:

let maybeName: String? = readLine()

if let name = maybeName {
    print("Привіт, \(name)!")
}

// print(name) // ❌ name не існує тут

Swift ніби говорить: «Поза межами if я вже не гарантую, що значення було». Тому й не дає вам цієї змінної.

Затінення і коротка форма if let name

Тепер цікавий момент: дуже часто optional-змінна й «розгорнута» змінна логічно є одним і тим самим значенням, лише в різних станах. У Swift поширений патерн if let foo = foo, тобто «створи нову змінну з тим самим іменем, але вже без ?». Саме тому, що це трапляється дуже часто, у Swift зʼявився скорочений запис: if let foo { ... } — це синтаксичний цукор для if let foo = foo { ... }.

Приклад:

let name: String? = readLine()

if let name {
    // Тут name: String (без ?), і він затіняє зовнішню optional-змінну name: String?
    print("Імʼя: \(name)")
} else {
    print("Імʼя не введено")
}

Важливо розуміти ідею: це нова змінна всередині блока, а не «магічне перетворення типу». У Swift optional binding створює окреме значення в новій області видимості — це принципова частина моделі мови.

3. Кілька Optional і додаткові перевірки

Кілька Optional в одній умові

Частий біль новачка — «сходинки» з вкладених if:

let line = readLine()
if let line {
    let n = Int(line)
    if let n {
        print(n * 2)
    } else {
        print("Не число")
    }
} else {
    print("Немає введення")
}

Працює, але виглядає як під’їзд без ліфта. На щастя, Swift дає змогу витягувати кілька значень в одній умові через кому. Це підтримується синтаксисом умов if/guard/while: можна поєднувати зв’язування Optional і булеві перевірки в одному блоці.

Ось читабельніший варіант:

if let line = readLine(),
   let n = Int(line) {
    print(n * 2)
} else {
    print("Введіть ціле число")
}

Тут ми робимо дві перевірки поспіль: «рядок взагалі прийшов» і «рядок успішно перетворився на число». Якщо щось не так — переходимо в else.

if let плюс смислові умови

Іноді мало просто «чи є значення». Ми хочемо, щоб воно було ще й «правильним» за логікою: додатним, непорожнім, у діапазоні тощо. Swift дає змогу додавати звичайні булеві умови до того самого списку через кому. Це один із тих моментів, де читабельність залежить від міри: 2–3 умови зазвичай нормально, а 10 умов перетворюють код на ребус.

Приклад: читаємо число і переконуємося, що воно додатне.

if let line = readLine(),
   let n = Int(line),
   n > 0 {
    print("Додатне число: \(n)")
} else {
    print("Потрібно ввести додатне ціле число")
}

Це працює тому, що умовні конструкції в Swift сприймають список перевірок як послідовність кроків, розділених комами.

4. Мінісхема: що робить if let

Щоб не сприймати if let як магічне закляття, корисно тримати в голові один і той самий шаблон: Swift намагається «дістати» значення з коробки. Якщо коробка порожня (nil) — переходимо в else.

flowchart TD
    A["Є Optional, наприклад Int?"] --> B{"Він nil?"}
    B -- "Так" --> C["else: обробляємо відсутність значення"]
    B -- "Ні" --> D["if: створюємо нову змінну без '?'"]
    D --> E["Працюємо як зі звичайним Int або String"]

5. Приклад: консольний «TipBuddy»

Щоб тема не залишилася «у вакуумі», давайте зберемо невеликий застосунок, який ми зможемо вдосконалювати в наступних лекціях цього рівня. Ідея проста: користувач вводить суму рахунку й відсоток чайових, а застосунок друкує підсумок. Ця вправа ідеально підходить для if let, тому що введення може бути відсутнім, а перетворення рядка на число може не спрацювати.

Крок 1: читаємо суму рахунку як Double

Спочатку зробимо найпряміший варіант: одне введення — одна перевірка — один розрахунок.

import Foundation

print("Введіть суму рахунку:")
let billLine = readLine()

if let billLine,
   let bill = Double(billLine) {
    print("Рахунок: \(bill)")
} else {
    print("Сума введена некоректно")
}

Зверніть увагу: Double(billLine) повертає Double?, тому ми витягуємо і рядок, і число.

Крок 2: додаємо відсоток чайових

Тепер потрібно два значення. І тут якраз спрацьовує комбінований прийом if let ... , let ....

import Foundation

print("Сума рахунку:")
let billLine = readLine()

print("Відсоток чайових (наприклад 10):")
let tipLine = readLine()

if let billLine,
   let bill = Double(billLine),
   let tipLine,
   let tipPercent = Double(tipLine) {

    let total = bill + bill * tipPercent / 100
    print("Разом: \(total)")
} else {
    print("Помилка введення: потрібно ввести два числа")
}

Так, умова стала довшою. Але вона все ще лінійна: ми читаємо зверху вниз і бачимо, які кроки мають виконатися.

Крок 3: додамо смислову перевірку

Трохи предметної логіки: відʼємні чайові — це вже не калькулятор, а соціальний експеримент. Додамо перевірку:

import Foundation

print("Сума рахунку:")
let billLine = readLine()

print("Відсоток чайових:")
let tipLine = readLine()

if let billLine,
   let bill = Double(billLine),
   bill >= 0,
   let tipLine,
   let tipPercent = Double(tipLine),
   tipPercent >= 0 {

    let total = bill + bill * tipPercent / 100
    print("Разом: \(total)")
} else {
    print("Введіть невід’ємні числа")
}

Тут важливо помітити: булеві перевірки (bill >= 0) ідуть поруч із перевірками зв’язування, і це нормальний стиль Swift.

6. Наводимо лад: виносимо читання числа у функцію

Коли ви пишете консольні застосунки, швидко з’являється повторюваний код: «попросити введення → прочитати рядок → спробувати перетворити на число → якщо не вдалося, повідомити». Це чудовий момент, щоб застосувати функції й зробити код трохи охайнішим.

Зробимо функцію, яка намагається прочитати Double і повертає Double?. Це чесний контракт: «може спрацювати, а може й ні».

import Foundation

func readDouble(prompt: String) -> Double? {
    print(prompt)
    let line = readLine()

    if let line, let value = Double(line) {
        return value
    } else {
        return nil
    }
}

Тепер основний код простіший:

import Foundation

let bill = readDouble(prompt: "Сума рахунку:")
let tip = readDouble(prompt: "Відсоток чайових:")

if let bill,
   let tip,
   bill >= 0,
   tip >= 0 {

    let total = bill + bill * tip / 100
    print("Разом: \(total)")
} else {
    print("Помилка: перевірте введення")
}

Тут ми використовуємо той самий прийом, який обговорювали вище: if let bill { ... } — скорочення для if let bill = bill { ... }, тобто вилучення зі затіненням імені.
Якщо ця коротка форма поки що плутає вас — можна писати довгу. Це нормально. Головне, щоб ви розуміли, що відбувається.

7. Читабельність: типові патерни if let

Коли ви лише опановуєте зв’язування Optional, найімовірніше, будете писати або занадто багато вкладеності, або занадто багато «розумної компактності». Ні те ні інше не смертельно, але читабельність — це навичка, яка зростає швидше, якщо помічати патерни.

Ситуація Менш вдало Зазвичай читабельніше
Два Optional поспіль вкладений if всередині if if let a = ..., let b = ... {}
Хочемо «те саме імʼя» if let x = x {} (багато повторів) if let x {} (коротше)
Перевірка й вилучення if x != nil { x! } if let x { ... } (без ризику)

8. Типові помилки під час роботи з if let

Помилка №1: намагатися «дотиснути» компілятор замість того, щоб обробити nil.
Новачок часто бачить, що тип став String?, і починає боротися з питальним знаком, ніби це помилка: «Ну, давай я якось перетворю це на String». Але Optional — це не перешкода, а підказка про реальність даних. Якщо введення може бути відсутнім, для цього випадку має бути окремий сценарій у застосунку. if let саме про це: обрати поведінку для nil, а не вдавати, що його не буває.

Помилка №2: використовувати змінну з if let поза блоком.
Це дуже часте «чому Swift такий суворий?». Змінна, створена в if let, живе у своїй області видимості, тому що тільки там гарантовано наявність значення. Якщо вам потрібно використовувати її далі, це зазвичай знак, що структуру коду варто перебудувати: перенести використання всередину if або винести логіку у функцію, яка повертає звичайний результат.

Помилка №3: робити «сходи» з вкладених if, хоча можна об’єднати умови.
Вкладені if let технічно коректні, але вони швидко роздувають код і ускладнюють читання. Swift дає змогу витягувати кілька значень в одній умові через кому, і це один із найкорисніших прийомів для початківця.

Помилка №4: плутати скорочений if let x зі «зміною типу змінної».
Скорочення if let x { ... } — це просто цукор для if let x = x { ... }. Усередині блока створюється нова змінна, яка затіняє зовнішню optional-змінну з тим самим іменем. Це не магічне «перемикання типу», а цілком конкретне оголошення нової змінної в новій області видимості.

Помилка №5: перетворювати умову на простирадло з 12 перевірок.
Swift дає змогу поєднувати зв’язування й булеві умови в одному if, і це потужно. Але якщо умова перетворюється на пів екрана, мозок читача починає «підвисати». У такій ситуації краще зробити кілька проміжних змінних або винести частину логіки в невелику функцію — особливо якщо перевірка має смислову назву («валідний відсоток», «непорожній рядок» тощо).

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ