Throw та Throws — механізми викиду та декларації винятків у мовах програмування. Оператор throw перериває нормальне виконання функції та передає об’єкт помилки вгору по стеку викликів. Ключове слово throws у сигнатурі функції попереджає сторону, що викликає, про можливість помилки, роблячи код передбачуваним. За даними Kotlin Documentation (2026), throw у Kotlin є виразом (expression), а не інструкцією, що дозволяє використовувати його всередині when-блоків та елвіс-оператора.
Головне
Throw — це оператор, який генерує виняток у точці виконання програми. Коли зустрічається throw, поточний потік виконання негайно переривається, і керування передається найближчому обробнику catch у стеку викликів. Якщо обробник не знайдено, застосунок аварійно завершується. Throw у мобільній розробці використовується для сигналізації про помилки, які неможливо обробити на поточному рівні абстракції — наприклад, невірна відповідь сервера, відсутність мережі або некоректні аргументи.
Throws — модифікатор у сигнатурі функції (в основному у Swift), який оголошує, що функція може викинути помилку. Це частина механізму checked errors у Swift: сторона, що викликає, зобов’язана обробити помилку через do-catch, try?, try! або позначити свою функцію як throws для подальшої передачі (propagation). У Kotlin та Java throws також існує, але Kotlin вважає його надлишковим — усі винятки в Kotlin unchecked, тобто можуть бути не оброблені без синтаксичного примусу. За даними Apple Swift Documentation (2026), throws у Swift — єдиний спосіб явно декларувати можливість помилки в типі функції, що робить API контракти прозорими для розробника.
Різниця між throw та throws фундаментальна: throw — це дія (викид винятку в runtime), throws — це декларація (compile-time контракт). Функція без throws не може використовувати throw — компілятор Swift видасть помилку. Функція з throws може не використовувати throw — це дозволено, але безглуздо. Такий розподіл робить throw/throws потужним інструментом проектування API, де контракт на помилку видно в сигнатурі функції до її виклику.
У Swift оператор throw приймає будь-який тип, що реалізує протокол Error. Найчастіше це enum з кейсами для різних видів помилок. Swift не підтримує checked exceptions у Java-стилі — натомість тип функції позначається throws, а обробка покладається на сторону, що викликає. Це робить throw у Swift більш гнучким, але й більш відповідальним для розробника.
Будь-який тип, що відповідає протоколу Error, може бути викинутий через throw. Найчастіше розробники використовують enum з кейсами без асоційованих значень (для простих помилок) або з associated values (для передачі контексту). Swift не вимагає, щоб помилка була enum — можна використовувати struct або class, що реалізують Error, але enum є кращим завдяки exhaustive switch на стороні обробника. Компілятор перевіряє, що всі кейси оброблені в do-catch.
enum AuthError: Error {
case invalidCredentials
case tokenExpired
case accountLocked(remainingMinutes: Int)
}
func login(username: String, password: String) throws -> Session {
guard isValid(username) else {
throw AuthError.invalidCredentials
}
let response = try api.authenticate(username, password)
if response.isLocked {
throw AuthError.accountLocked(
remainingMinutes: response.lockDuration
)
}
return Session(token: response.token)
}
AuthError визначає три сценарії: невірні облікові дані, прострочений токен та заблокований акаунт з асоційованим значенням remainingMinutes. Функцію login оголошено як throws — компілятор вимагає викликати її через try. Усередині функції throw використовується у двох місцях: при невалідному username та при заблокованому акаунті. Асоційоване значення accountLocked дозволяє передати користувачеві конкретні дані — скільки хвилин чекати до розблокування. Такий підхід виключає необхідність в окремих API endpoints для перевірки статусу блокування.
У Kotlin throw — це вираз (expression) типу Nothing, а не інструкція. Це означає, що throw можна використовувати в правій частині присвоєння, всередині when-виразів та елвіс-оператора ?:. Тип Nothing — спеціальний підтип усіх типів у Kotlin, що дозволяє використовувати throw у місцях, де потрібне значення будь-якого типу. Компілятор розуміє, що після throw виконання не продовжується, і не вимагає гілки для цього випадку.
Nothing — унікальний тип у Kotlin, який є підтипом усіх можливих типів. Функція, що повертає Nothing (наприклад, TODO()), ніколи не завершується нормально — або завжди викидає виняток, або йде в нескінченний цикл. Це робить throw природним кандидатом для використання в місцях, де потрібне значення: елвіс-оператор, when без else, ініціалізація змінної. Якщо throw стоїть у гілці when, компілятор розуміє, що гілка веде до Nothing, і не вимагає return або else для цієї гілки.
data class Config(val apiUrl: String, val timeoutSec: Int)
class ConfigParser {
fun parse(json: String): Config {
val obj = JSONObject(json)
val url = obj.optString("apiUrl")
?: throw IllegalArgumentException("apiUrl is required")
val timeout = obj.optInt("timeoutSec", 30)
return Config(url, timeout)
}
fun getErrorMessage(code: Int): String {
return when (code) {
404 -> "Not found"
500 -> "Server error"
else -> throw IllegalArgumentException("Unknown code: $code")
}
}
}
У першому прикладі throw використовується в елвіс-операторі ?:: якщо поле apiUrl відсутнє в JSON, вираз throw негайно перериває виконання та викидає IllegalArgumentException. Тип Nothing дозволяє компілятору вивести тип правої частини як String (елвіс очікує String, throw має тип Nothing, Nothing — підтип String). У другому прикладі throw всередині when-виразу: якщо код не відповідає жодному з відомих, викидається виняток. Компілятор розуміє, що після throw код недосяжний, тому тип функції, що повертається, String не порушено.
У Swift throws вказується після списку параметрів та перед стрілкою типу, що повертається. Функція з throws може викликати лише інші throws-функції всередині do-catch або з try?. Якщо throws-функція не обробляє помилку, вона передає її стороні, що викликає. Swift також підтримує rethrows — модифікатор для функцій вищого порядку, які приймають замикання з throws та пробрасывают його помилку. rethrows означає, що функція викидає помилку лише якщо передане замикання її викинуло — сама функція помилку не генерує.
func mapValues<T>(
_ array: [T],
transform: (T) throws -> U
) rethrows -> [U] {
var result = [U]()
for element in array {
result.append(try transform(element))
}
return result
}
// Використання з throws-замиканням
let parsed = try mapValues(jsonStrings) { str in
let data = Data(str.utf8)
return try JSONDecoder().decode(Item.self, from: data)
}
rethrows дозволяє функції mapValues бути гнучкою: вона приймає як throws-замикання, так і звичайні. Якщо передано throws-замикання, виклик mapValues вимагає try; якщо звичайне — try не потрібен. Це робить rethrows ідеальним для функцій вищого порядку, таких як map, filter, reduce, у стандартній бібліотеці Swift. Рекомендація Apple: використовуйте rethrows для API, які приймають throws-замикання та єдиним джерелом помилки є це замикання. Якщо функція може викинути власну помилку, використовуйте throws.
Swift throws ближче до checked exceptions (як у Java) — помилки, що викидаються, декларуються в сигнатурі. Kotlin та Dart використовують unchecked винятки — throws у сигнатурі не потрібен. Різниця принципова: checked примушує розробника до обробки (безпечніше, але багатослівніше), unchecked дає свободу, але підвищує ризик забути обробити помилку. Swift обрав checked для throws, Kotlin — unchecked для всіх винятків. Обидва підходи мають переваги: Swift надійніший на рівні мови, Kotlin компактніший та зручніший у ланцюжках функціональних перетворень.
Для структурованої обробки помилок у мобільних застосунках рекомендується створювати власні типи помилок замість використання базових Exception або Error. У Swift для цього використовується enum з протоколом Error, у Kotlin — sealed class з наслідуванням від Throwable (або від Exception), у Dart — class з наслідуванням від Exception. Користувацькі типи дозволяють групувати помилки за категоріями та передавати асоційовані дані.
| Мова | Тип помилки | Особливість |
|---|---|---|
| Swift | enum: Error { ... } | Асоційовані значення, exhaustive switch у catch |
| Kotlin | sealed class : Throwable() | Data class для помилок з полями, when-вираз |
| Dart | class implements Exception | Message-поле, on-клауза в catch |
| Java | class extends Exception | Checked vs unchecked, обов’язковий throws у сигнатурі |
При проектуванні користувацьких помилок дотримуйтесь правила: одна помилка — один сценарій. Не об’єднуйте різні причини в один тип з прапорцем String message — робіть окремі кейси/підкласи для кожного сценарію. Це дозволить стороні, що викликає, обробляти кожен випадок через pattern matching (when/switch), а не через порівняння рядків. У Swift це дає exhaustive checking — компілятор попередить, якщо не оброблений якийсь кейс enum NetworkError.
sealed class NetworkError(val message: String) : Throwable(message) {
data class Timeout(val durationMs: Long) :
NetworkError("Request timed out after ${durationMs}ms")
data class HttpError(val code: Int, val body: String?) :
NetworkError("HTTP $code")
data class NoConnection(val cause: IOException) :
NetworkError("No internet connection")
}
Sealed class NetworkError наслідується від Throwable (стандартний тип винятків у Kotlin). Кожен підклас — data class з власними полями: Timeout містить тривалість таймауту в мілісекундах, HttpError — код та тіло відповіді, NoConnection — вихідний IOException. Такий дизайн дозволяє обробляти кожну помилку через when з вичерпним перебором (коли додаєте новий підклас, компілятор змусить оновити всі when-вирази).
Swift пропонує три варіанти виклику throws-функцій, кожен зі своїм контрактом безпеки. try — стандартний спосіб: вимагає do-catch або перебування всередині throws-функції. try? перетворює помилку на nil — результат стає опціональним, при помилці повертається nil, тип змінюється з T на T?. try! — примусове виконання без обробки: якщо помилка викидається, застосунок крашиться. Використовуйте try! лише коли абсолютно впевнені, що помилка неможлива (наприклад, свідомо коректні дані).
let configPath = Bundle.main.path(forResource: "config", ofType: "json")!
// try? — опціональний результат
let data = try? Data(contentsOf: URL(fileURLWithPath: configPath))
let json = try? JSONSerialization.jsonObject(with: data ?? Data())
// try! — гарантований успіх (тільки коли впевнені)
let decoder = JSONDecoder()
let defaultConfig = try! decoder.decode(
Config.self,
from: Config.defaultJSON
)
// try — стандартна обробка
do {
let user = try fetchUser()
showUser(user)
} catch let error as NetworkError {
showRetryAlert(error.message)
}
У прикладі try! використовується для свідомо валідного JSON, вбудованого в bundle застосунку — помилка декодування неможлива при коректному релізі. try? застосовується для читання файлу конфігурації — якщо файл відсутній або пошкоджений, застосунок використовує значення за замовчуванням, а не падає. try в do-catch використовується для мережевих запитів, де помилка очікувана та вимагає реакції користувача. Рекомендація: уникайте try! у production коді — використовуйте лише для константних даних, перевірених на етапі збірки.
Часті запитання
Throw — оператор, який викидає помилку під час виконання програми, перериваючи потік. Throws — модифікатор сигнатури функції, що оголошує, що функція може викинути помилку. Функція без throws не може використовувати throw. Throws — це compile-time контракт, throw — runtime-дія.
Kotlin слідує філософії unchecked exceptions: усі винятки можуть бути не оброблені без синтаксичного примусу. Розробники Kotlin вважають, що throws у Java призводить до надлишкових try-catch блоків та ігнорування checked exceptions через порожні catch. Nothing-тип Kotlin дозволяє використовувати throw як вираз, замінюючи throws більш гнучким способом.
try! допустимий лише коли ви абсолютно впевнені, що помилка неможлива: свідомо валідний JSON із bundle, константні дані, коректні URL схеми. У production коді try! — виняток, а не правило. try? кращий для опціональних сценаріїв з fallback-значенням, try з do-catch — для обов’язкової обробки помилок.
Rethrows — модифікатор для функцій, що приймають throws-замикання. Функція з rethrows викидає помилку лише якщо передане замикання її викинуло. Це дозволяє функціям вищого порядку (map, filter) працювати як з throws, так і з non-throws замиканнями без примусового try на стороні, що викликає.
Так, всередині catch можна використовувати throw для пробросу помилки вище по стеку, загорнувши її в інший тип або додавши контекст. Це називається error chaining або rethrow. У Swift для цього достатньо ще одного throw всередині catch, у Kotlin — throw всередині catch-блоку. Блок finally виконається перед передачею керування далі.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також