[kotlin] inline 함수란 뭘까...

두 개의 함수가 있다.

fun printHello() {
    println("Hello")
}

fun main() {
    printHello()
}

 

이 함수에서 굳이 printHlello라는 이름이 필요할까?

fun main() { println("Hellp") } 와 다를바가 전혀 없는 코드인데 말이다.

 

이 발상이 코드화 된 게 inline 함수이다. 

inline fun printHello() {
    println("Hello")
}

fun main() {
    printHello()
}

 

코드의 모양 자체는 다르지 않다. 그러나 컴파일 된 코드의 모양은 다르다. 어떻냐면

fun main() {
    println("Hello")
}

이렇듯 main에 println("Hello")를 그대로 복사해버린다. inline이 붙었기 때문이다.


왜 등장했을까?

 

  • C 언어 (1980년대)
    inline 키워드는 없었지만, 컴파일러 최적화 중 하나로 함수 인라인화(function inlining)를 자동으로 적용했다.
    (예: 짧은 함수는 알아서 코드 복사)
  • C++ (1989년)
    공식적으로 inline 키워드를 도입했다.
    → "컴파일러야, 이 함수는 호출하지 말고 복사해!"라고 명시하는 방법이었다.
  • Java (1995년)
    inline 키워드 자체는 없지만,
    JIT(Just-In-Time) 컴파일러가 **핫스팟(자주 쓰는 짧은 함수)**을 알아서 인라인 최적화했다.
  • Kotlin (2016년 출시)
    Kotlin은 inline 키워드를 언어 차원에서 다시 적극적으로 지원했다.
    특히 람다(고차 함수)를 효율적으로 처리하기 위해 명시적인 inline을 사용하게 만들었다.

Kotlin이 inline을 강조한 이유는 특별하다.

  • Kotlin은 **람다식(익명 함수)**을 많이 쓰기 때문에,
  • 람다 전달/호출 비용을 줄이기 위해 inline이 핵심 최적화 수단으로 자리 잡았다.

예제를 보자.

 

inline을 사용하지 않고, 일반적인 람다를 사용할 경우.
fun callLambda(lambda: () -> Unit) {
    lambda()
}

callLambda { println("Hello") }

코틀린은 함수의 인자로 람다를 전달하면, 내부적으로 익명 클래스 객체를 하나 만든다.

이 코드는 실제로 컴파일될 때 Runnable 비슷한 익명 객체가 만들어지고, 그걸 callLambda에 넘긴다.

  • 객체 생성(메모리 소모)
  • 함수 호출 (성능 비용)
inline 함수를 사용한 람다
inline fun callLambda(lambda: () -> Unit) {
    lambda()
}

이렇게 inline을 붙이면, 람다를 객체로 만들지 않는다.
그 대신 람다 코드 자체를 함수 본문에 복붙해버린다.

즉, 컴파일 시점에 이렇게 변환된다.

println("Hello")

→ 객체 생성 비용 0, 호출 비용 0


✅ 1. noinline

📌 상황

inline 함수 안에서 람다 파라미터를 람다 변수로 저장하거나 다른 함수로 전달할 경우

inline fun example(a: () -> Unit, b: () -> Unit) { val store = b // ❌ 컴파일 에러 }

위 코드는 b가 inline 람다여서, 컴파일 시점에 복붙되기 때문에
참조할 수 있는 객체가 없다.

📌 해결책 → noinline 붙이기

inline fun example(a: () -> Unit, noinline b: () -> Unit) { val store = b // ✅ OK }
→ noinline을 붙이면 b는 inline 되지 않고 객체로 유지

→ 변수로 저장하거나 다른 함수로 전달 가능함


✅ 2. crossinline

📌 상황

inline 함수에 넘긴 람다 안에서 return을 사용하면
호출한 곳에서 return이 되어버림

inline fun doSomething(block: () -> Unit) { block() } fun run() { doSomething { return // ❌ 전체 run() 함수에서 빠져나감 } }

위처럼 return이 doSomething이 아니라 run()을 종료시켜버림

→ 이건 non-local return 이라고 부름


📌 해결책 → crossinline 붙이기

inline fun doSomething(crossinline block: () -> Unit) { val task = Runnable { block() // ✅ 이제 return 사용 불가 } task.run() }

→ crossinline을 붙이면 람다에서 return을 막고, 다른 쓰레드나 클래스에서 안전하게 호출할 수 있음

 

inline 람다 복붙해서 성능 최적화 성능 중요할 때 사용
noinline 람다를 객체로 유지함 람다를 변수로 저장하거나 넘길 때
crossinline 람다 안에서 return 못하게 막음 다른 쓰레드나 클래스에서 실행할 때

 

noinline → 람다를 "복붙하지 말고" 객체로 남겨둬!
crossinline → 람다 안에서 "return 쓰지 마!"