문제
아래의 q2NonLocal()과 q2Labeled()를 차례로 호출하면 출력이 어떻게 될까?
각 함수를 차근차근 읽으면서 println 시퀀스를 예측해보세요.
그리고 두 함수의 결과가 어떻게, 왜 다른지 비교해보세요.
internal inline fun repeat3(block: (Int) -> Unit) {
for (i in 0..2) {
println("before $i")
block(i)
}
}
private fun q2NonLocal() {
println("-- non-local return --")
repeat3 {
println("iter $it")
if (it == 1) return
}
println("q2NonLocal end")
}
private fun q2Labeled() {
println("-- labeled return --")
repeat3 {
println("iter $it")
if (it == 1) return@repeat3
}
println("q2Labeled end")
}
fun runQ2() {
println("=== Q2: inline return ===")
q2NonLocal()
println("after q2NonLocal()")
q2Labeled()
println("after q2Labeled()")
}
힌트
- 힌트 보기
inline함수 안의 람다에서return은 람다가 아니라 호출한 함수 자체에서 리턴한다 (non-local return).return@레이블은 람다 블록에서만 리턴한다 (local return).
예측
=== Q2: inline return ===
-- non-local return --
before 0
iter 0
before 1
iter 1
after q2NonLocal()
-- labeled return --
before 0
iter 0
before 1
iter 1
q2Labeled end
after q2Labeled()
실제 관측
=== Q2: inline return ===
-- non-local return --
before 0
iter 0
before 1
iter 1
after q2NonLocal()
-- labeled return --
before 0
iter 0
before 1
iter 1
before 2
iter 2
q2Labeled end
after q2Labeled()
왜 그럴까?
예측에서 return@repeat3 가 repeat3 함수를 반환시킨다고 생각했으나, 실제로는 “repeat3” 람다에서 반환되었다.
그래서, repeat3 에 있는 for가 계속 실행된 것이다. 람다의 implicit label은 람다 자체가 아니라 “그 람다가 전달된 함수의 이름”으로 자동으로 부여된다.
implicit label
코드에서는 return@repeat3 이 repeat3 함수를 종료하는 것 처럼 읽히지만, 코틀린 문법 상, 이 라벨은 람다 표현식에 자동으로 붙은 이름이다.
q2Labeled 가 호출하는repeat3 는 아래와 같은 구조를 갖는다고 말 할 수 있다.
for (i in 0..2) {
println("before $i")
println("iter $i")
if (i == 1) continue
}
inline 함수
repeat3는 inline 함수이므로, 람다도 인라인되어 호출자의 코드 본문에 펼쳐진다. 그렇기에, q2NonLocal 에서 repeat3 람다에서 return할 수 있는 이유다. 실제로 inline을 삭제하면 더이상 람다 안에서 return 할 수 없고, implicit label이 강제된다.

대표적인 inline 함수
fun findAdmin(users: List<User>): User? {
users.forEach { user ->
if (user.isAdmin) return user
}
return null
}
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
우리가 자주 사용하던 forEach도 inline 함수이다. 따라서, 위의 retrun user는 forEach에서 반환하는 게 아니라 findAdmin에서 반환된다. 대부분 inline과 implicit label에 대한 사전 지식 없이도 자연스럽게 사용하고 있었겠지만, 내부는 inline을 사용해서 구현되고 있었다.
정리
| 패턴 | return 동작 |
비유 |
|---|---|---|
inline 람다 + return |
non-local: 바깥 함수 종료 | 외부 함수의 return |
inline 람다 + return@label |
local: 람다 1회만 종료 | continue |
non-inline 람다 + return |
컴파일 에러 | — |
non-inline 람다 + return@label |
local | continue |
익명 함수 + return |
local: 익명 함수만 종료 | continue |
'안드로이드' 카테고리의 다른 글
| 비동기에서 suspendCancellableCoroutine까지, 코루틴이 정말 하는 일 (0) | 2026.05.18 |
|---|---|
| 안드로이드 원정대 - 왜 Intent가 필요할까?, Activity, IPC, Bundle (0) | 2026.05.06 |
| Q1. by lazy + 순환 초기화 (0) | 2026.04.22 |
| 리컴포지션 원정대 - Restart Scope, inline Composable (0) | 2026.04.19 |
| 리컴포지션 원정대 - Compose 리컴포지션 스코프와 remember의 관계 (0) | 2026.04.13 |