Q2. inline + non-local return vs labeled return

조회

문제

아래의 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@repeat3repeat3 함수를 반환시킨다고 생각했으나, 실제로는 “repeat3” 람다에서 반환되었다.

그래서, repeat3 에 있는 for가 계속 실행된 것이다. 람다의 implicit label은 람다 자체가 아니라 “그 람다가 전달된 함수의 이름”으로 자동으로 부여된다.

implicit label

코드에서는 return@repeat3repeat3 함수를 종료하는 것 처럼 읽히지만, 코틀린 문법 상, 이 라벨은 람다 표현식에 자동으로 붙은 이름이다.

q2Labeled 가 호출하는repeat3 는 아래와 같은 구조를 갖는다고 말 할 수 있다.

for (i in 0..2) {
    println("before $i")

    println("iter $i")
    if (i == 1) continue
}

inline 함수

repeat3inline 함수이므로, 람다도 인라인되어 호출자의 코드 본문에 펼쳐진다. 그렇기에, 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 userforEach에서 반환하는 게 아니라 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

댓글

홈으로 돌아가기

검색 결과

"search" 검색 결과입니다.