코틀린 언어 용어 사전
관용구let · run · apply · also · with

스코프 함수

객체를 블록에 넘겨 다루는 다섯 함수. 수신자를 it으로 받는지 this로 받는지, 반환이 자신인지 결과인지로 구분한다.

객체를 블록에 넘겨 다루는 다섯 함수. 코틀린에서 가장 자주 쓰는 도구다.

두 축으로 외운다

              수신자 접근      반환값
let           it              블록 결과
run           this            블록 결과
with          this            블록 결과   (확장 함수가 아니다)
apply         this            객체 자신
also          it              객체 자신

반환이 '객체 자신' → 체이닝·설정에 쓴다 (apply · also) 반환이 '블록 결과' → 변환에 쓴다 (let · run · with)

각각의 전형적 쓰임

// let — null 아닐 때만 · 변환
user?.let { showProfile(it) }
val len = text?.let { it.trim().length } ?: 0

// apply — 객체를 설정하고 그 객체를 반환
val intent = Intent(this, DetailActivity::class.java).apply {
    putExtra("id", userId)
    flags = Intent.FLAG_ACTIVITY_NEW_TASK
}

// also — 부수 효과 (로깅 · 검증). 체인을 끊지 않는다
val result = compute().also { Log.d("TAG", "결과: $it") }

// run — 객체 컨텍스트에서 계산해 결과를 얻는다
val area = rect.run { width * height }

// with — 같은 객체에 여러 번 접근할 때 (확장이 아니라 인자로 받는다)
with(binding) {
    title.text = user.name
    subtitle.text = user.email
}

고르는 기준

  • 객체를 설정만 하고 그대로 쓴다 — → apply
  • 중간에 로그만 남기고 흐름은 유지한다 — → also
  • null 검사와 함께 변환한다 — → let
  • 같은 객체의 멤버를 여러 번 쓴다 — → with (또는 run)

중첩하면 읽기 어려워진다

// ❌ it 이 무엇을 가리키는지 헷갈린다
a?.let { b?.let { c?.let { ... } } }

// ✅ 이름을 붙이거나 구조를 바꾼다
if (a != null && b != null && c != null) { ... }
// 또는
val x = a ?: return
val y = b ?: return

takeIf·takeUnless

val valid = email.takeIf { it.contains("@") }      // 조건에 맞으면 자신, 아니면 null
val blank = text.takeUnless { it.isBlank() }

// Elvis 와 짝을 이룬다
val name = input.takeIf { it.isNotBlank() } ?: "익명"

면접 함정

  • "apply와 also는 같다" → 수신자 접근 방식이 다르다(this vs it). 바깥 this와 이름이 겹칠 때 also가 안전하다.
  • "스코프 함수를 쓰면 코드가 좋아진다" → 중첩되면 오히려 나빠진다. 한 겹까지가 안전선이다.

반환값을 잘못 쓰는 실수

// ❌ apply 는 객체를 반환한다 — 계산 결과를 기대하면 틀린다
val len = text.apply { length }        // len 은 text 다 (String)

// ✅ 계산 결과가 필요하면 let 또는 run
val len = text.let { it.length }
val len = text.run { length }

체이닝에서의 also

fun createUser(name: String) = User(name)
    .also { logger.info("생성: $it") }      // 흐름을 끊지 않고 부수 효과만
    .also { analytics.track("user_created") }

run의 두 얼굴

val x = obj.run { ... }      // 확장 함수 — 수신자가 obj
val y = run { ... }          // 최상위 함수 — 그냥 블록을 실행해 값을 얻는다

// 후자는 복잡한 초기화에 유용하다
val config = run {
    val base = loadBase()
    val override = loadOverride()
    base + override
}

언제 안 쓰나

  • 그냥 지역 변수를 쓰는 게 더 읽기 쉬울 때

  • 블록이 5줄을 넘어갈 때 (함수로 뽑는다)

  • 세 겹 이상 중첩될 때

  • "짧게 쓰려고" 가 아니라 "의도가 더 잘 드러나서" 쓰는 것이다

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 스코프 함수·위임 — let·run·apply·also·by·lazy