Что означает `return@` в Kotlin и как правильно использовать метки возврата?
Что такое метки возврата в Kotlin?
Конструкция return@label в Kotlin — это механизм нелокального возврата (non-local return). Она позволяет выйти не из самой лямбды, а из внешней функции, которая её вызвала, или из конкретной лямбды, если вы явно указываете метку.
Проблема в том, что в Kotlin лямбды не могут использовать обычный return, если они не встроены (inline). Обычный return внутри лямбды пытается завершить ближайшую объявленную функцию (например, fun main()). Именно поэтому ваш первый пример не компилируется — компилятор не знает, откуда именно вы хотите вернуться.
Как это работает?
Каждая лямбда, переданная inline-функции (например, forEach), автоматически получает метку, совпадающую с именем этой функции. Вы можете использовать эту метку после return@, чтобы указать, из какой именно лямбды вы хотите выйти.
Рассмотрим ваш пример с пояснениями:
fun main() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) {
return@forEach // Выход из лямбды forEach, продолжение main
}
println(it)
}
println("Готово") // Этот код выполнится
}
Вывод:
1
2
Готово
return@forEach— выходит из текущей итерации лямбдыforEach, но не из самой функцииmain.- Если бы вы написали просто
return, то вышли бы изmainполностью, и "Готово" не напечаталось бы.
Пример с RxJava и subscribe
В RxJava метод subscribe принимает лямбду. Если вы хотите прервать обработку одного элемента, но продолжить слушать поток, вы используете return@subscribe:
observable.subscribe { value ->
if (value.isInvalid()) {
return@subscribe // Пропускаем этот элемент, но подписка остаётся активной
}
process(value)
}
Без метки @subscribe компилятор выдаст ошибку, так как обычный return внутри лямбды не разрешён (если subscribe не inline, а он часто не inline).
Создание своих меток
Вы можете задавать свои метки с помощью синтаксиса label@ перед лямбдой:
fun main() {
listOf(1, 2, 3, 4, 5).forEach myLoop@{
if (it == 3) {
return@myLoop // Выход из лямбды с меткой myLoop
}
println(it)
}
}
Когда использовать?
- В inline-функциях (forEach, map, filter) — для выхода из лямбды, а не из внешней функции.
- В колбэках и слушателях (RxJava, корутины) — для прерывания обработки текущего события.
- Во вложенных лямбдах — чтобы точно указать, из какой именно лямбды вы выходите.
Альтернатива: явный return
Если вы хотите выйти из внешней функции, используйте простой return. Например:
fun main() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) {
return // Выход из main
}
println(it)
}
println("Не будет напечатано")
}
Вывод: 1 2. Функция main завершится досрочно.
Запомните главное правило: return@label — возврат из лямбды с меткой, обычный return — возврат из ближайшей объявленной функции (fun).
Простое объяснение: метки как goto для лямбд
Можно представить метки в Kotlin как аналог break или continue для циклов, только для лямбд. Когда вы пишете return@forEach, это похоже на continue в цикле — вы пропускаете текущую итерацию и переходите к следующей.
Есть ещё один полезный приём — использование run с меткой для группировки операций:
fun processUser(user: User?) {
user?.let { u ->
if (u.name.isNullOrEmpty()) return@let // Выход из let, если имя пустое
println("Обрабатываем ${u.name}")
}
println("Продолжаем работу")
}
Если вы работаете с корутинами, там тоже есть похожий механизм:
suspend fun fetchData() = withContext(Dispatchers.IO) {
val data = loadFromNetwork()
if (data == null) return@withContext // Выход из корутины, но не из внешней функции
saveToDatabase(data)
}
Совет: если вы часто путаетесь, используйте явные метки (myLabel@), чтобы код был читаемее. И помните: если лямбда не inline (например, в RxJava subscribe), то return без метки не скомпилируется — это защита от случайного выхода из внешней функции.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги
Безопасность C# приложений: топ-10 уязвимостей и защита в .NET