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