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/threads мощным инструментом проектирования 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 естественным кандидатом для использования в местах, где требуется значение: elvis-оператор, 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-выражения: если код не соответствует ни одному из известных, выбрасывается исключение. Компилятор понимает, что after 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 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

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