Throw/Throws — що це, оператори викиду винятків та як працюють у мобільній розробці

Автор: IT Sectr Опубліковано: 2026-05-26 Час читання: 9 хв

Throw та Throws — механізми викиду та декларації винятків у мовах програмування. Оператор throw перериває нормальне виконання функції та передає об’єкт помилки вгору по стеку викликів. Ключове слово throws у сигнатурі функції попереджає сторону, що викликає, про можливість помилки, роблячи код передбачуваним. За даними Kotlin Documentation (2026), throw у Kotlin є виразом (expression), а не інструкцією, що дозволяє використовувати його всередині when-блоків та елвіс-оператора.

Головне

  • Throw — оператор, що перериває виконання функції та передає об’єкт помилки вгору по стеку викликів
  • Throws — ключове слово в сигнатурі функції, що оголошує, що функція може викинути виняток
  • У Swift функція позначається throws, виклик вимагає try/try?/try!, а throw приймає будь-який тип, що реалізує протокол Error
  • У Kotlin throw — вираз типу Nothing, throws відсутній (усі винятки unchecked)
  • Користувацькі помилки створюються через enum (Swift) або sealed class (Kotlin) для зручності обробки в catch

Що таке Throw і Throws?

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, де контракт на помилку видно в сигнатурі функції до її виклику.

Throw у Swift

У Swift оператор throw приймає будь-який тип, що реалізує протокол Error. Найчастіше це enum з кейсами для різних видів помилок. Swift не підтримує checked exceptions у Java-стилі — натомість тип функції позначається throws, а обробка покладається на сторону, що викликає. Це робить throw у Swift більш гнучким, але й більш відповідальним для розробника.

Протокол Error та типи помилок

Будь-який тип, що відповідає протоколу Error, може бути викинутий через throw. Найчастіше розробники використовують enum з кейсами без асоційованих значень (для простих помилок) або з associated values (для передачі контексту). Swift не вимагає, щоб помилка була enum — можна використовувати struct або class, що реалізують Error, але enum є кращим завдяки exhaustive switch на стороні обробника. Компілятор перевіряє, що всі кейси оброблені в do-catch.

swift
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 для перевірки статусу блокування.

Throw у Kotlin

У Kotlin throw — це вираз (expression) типу Nothing, а не інструкція. Це означає, що throw можна використовувати в правій частині присвоєння, всередині when-виразів та елвіс-оператора ?:. Тип Nothing — спеціальний підтип усіх типів у Kotlin, що дозволяє використовувати throw у місцях, де потрібне значення будь-якого типу. Компілятор розуміє, що після throw виконання не продовжується, і не вимагає гілки для цього випадку.

Nothing-тип та його роль у композиції

Nothing — унікальний тип у Kotlin, який є підтипом усіх можливих типів. Функція, що повертає Nothing (наприклад, TODO()), ніколи не завершується нормально — або завжди викидає виняток, або йде в нескінченний цикл. Це робить throw природним кандидатом для використання в місцях, де потрібне значення: елвіс-оператор, when без else, ініціалізація змінної. Якщо throw стоїть у гілці when, компілятор розуміє, що гілка веде до Nothing, і не вимагає return або else для цієї гілки.

kotlin
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 не порушено.

Throws у сигнатурі функції та rethrows

У Swift throws вказується після списку параметрів та перед стрілкою типу, що повертається. Функція з throws може викликати лише інші throws-функції всередині do-catch або з try?. Якщо throws-функція не обробляє помилку, вона передає її стороні, що викликає. Swift також підтримує rethrows — модифікатор для функцій вищого порядку, які приймають замикання з throws та пробрасывают його помилку. rethrows означає, що функція викидає помилку лише якщо передане замикання її викинуло — сама функція помилку не генерує.

swift
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.

Checked vs unchecked винятки

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. Користувацькі типи дозволяють групувати помилки за категоріями та передавати асоційовані дані.

МоваТип помилкиОсобливість
Swiftenum: Error { ... }Асоційовані значення, exhaustive switch у catch
Kotlinsealed class : Throwable()Data class для помилок з полями, when-вираз
Dartclass implements ExceptionMessage-поле, on-клауза в catch
Javaclass extends ExceptionChecked vs unchecked, обов’язковий throws у сигнатурі

При проектуванні користувацьких помилок дотримуйтесь правила: одна помилка — один сценарій. Не об’єднуйте різні причини в один тип з прапорцем String message — робіть окремі кейси/підкласи для кожного сценарію. Це дозволить стороні, що викликає, обробляти кожен випадок через pattern matching (when/switch), а не через порівняння рядків. У Swift це дає exhaustive checking — компілятор попередить, якщо не оброблений якийсь кейс enum NetworkError.

Приклад користувацької помилки в Kotlin

kotlin
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-вирази).

Try, try? та try! у Swift

Swift пропонує три варіанти виклику throws-функцій, кожен зі своїм контрактом безпеки. try — стандартний спосіб: вимагає do-catch або перебування всередині throws-функції. try? перетворює помилку на nil — результат стає опціональним, при помилці повертається nil, тип змінюється з T на T?. try! — примусове виконання без обробки: якщо помилка викидається, застосунок крашиться. Використовуйте try! лише коли абсолютно впевнені, що помилка неможлива (наприклад, свідомо коректні дані).

swift
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 у Swift?

Throw — оператор, який викидає помилку під час виконання програми, перериваючи потік. Throws — модифікатор сигнатури функції, що оголошує, що функція може викинути помилку. Функція без throws не може використовувати throw. Throws — це compile-time контракт, throw — runtime-дія.

Чому в Kotlin немає throws?

Kotlin слідує філософії unchecked exceptions: усі винятки можуть бути не оброблені без синтаксичного примусу. Розробники Kotlin вважають, що throws у Java призводить до надлишкових try-catch блоків та ігнорування checked exceptions через порожні catch. Nothing-тип Kotlin дозволяє використовувати throw як вираз, замінюючи throws більш гнучким способом.

Коли використовувати try! у Swift?

try! допустимий лише коли ви абсолютно впевнені, що помилка неможлива: свідомо валідний JSON із bundle, константні дані, коректні URL схеми. У production коді try! — виняток, а не правило. try? кращий для опціональних сценаріїв з fallback-значенням, try з do-catch — для обов’язкової обробки помилок.

Що таке rethrows у Swift?

Rethrows — модифікатор для функцій, що приймають throws-замикання. Функція з rethrows викидає помилку лише якщо передане замикання її викинуло. Це дозволяє функціям вищого порядку (map, filter) працювати як з throws, так і з non-throws замиканнями без примусового try на стороні, що викликає.

Чи можна викинути помилку всередині блоку catch?

Так, всередині catch можна використовувати throw для пробросу помилки вище по стеку, загорнувши її в інший тип або додавши контекст. Це називається error chaining або rethrow. У Swift для цього достатньо ще одного throw всередині catch, у Kotlin — throw всередині catch-блоку. Блок finally виконається перед передачею керування далі.

Підсумки

  • Throw — оператор викиду винятку, що перериває поточний потік та передає об’єкт помилки вгору по стеку
  • Throws — compile-time декларація в сигнатурі функції про можливість помилки, обов’язкова у Swift, відсутня в Kotlin
  • У Swift throw приймає enum: Error, функція позначається throws, викликається через try / try? / try!
  • У Kotlin throw — вираз типу Nothing, що допускає використання в when, елвіс-операторі та присвоєннях
  • Rethrows у Swift дозволяє функціям вищого порядку пробрасывати помилки лише від переданих замикань
  • Користувацькі помилки створюються через enum (Swift) або sealed class (Kotlin) з асоційованими значеннями для детальної інформації
  • Проектуйте типи помилок за принципом "один кейс — один сценарій" для зручності обробки через pattern matching

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також