Kotlin path · Functions and types · Topic 10 of 13

Scope functions: let, run, apply, also, with

Not started

The standard library has five functions whose only job is to run a block of code with an object in scope: let, run, with, apply and also. None of them does anything you couldn't write with a local variable. They are all small inline functions, so they cost nothing at runtime; what they cost is readability, if you pick the wrong one or stack them.

Why it matters: Scope functions make some code shorter and other code unreadable. Two questions (is the object it or this, and what comes back) tell them apart and tell you when to stop.

  1. 1it or this

    let and also pass the object as it; run, apply and with make it this, so its members can be called directly.

  2. 2What comes back

    apply and also return the object itself; let, run and with return the lambda's result.

  3. 3One job each

    apply to configure, also for side effects, let to transform or handle a nullable, run/with to compute from an object.

  4. 4Don't nest them

    Two levels of it and this are hard to read; a local val is often clearer.

1

How it works

The explanation, with examples you can run.

Two questions

FunctionObject inside the blockReturns
letitthe block's result
alsoitthe object
runthisthe block's result
applythisthe object
with(obj)thisthe block's result (not an extension)

this is a lambda with a receiver (T.() -> R): you can call the object's members without a prefix. it is a plain parameter ((T) -> R), which you can rename (let { user -> ... }).

One typical use each

data class Request(var url: String = "", var timeoutMs: Int = 0, val headers: MutableMap<String, String> = mutableMapOf()) fun main() {    // apply: configure an object, get the object back    val req = Request().apply {        url = "https://example.com"        timeoutMs = 5_000        headers["Accept"] = "application/json"    }     // also: a side effect in the middle of a chain, without changing the value    val ids = listOf(3, 1, 2).sorted().also { println("sorted: $it") }     // let: work with a nullable value, or transform one value into another    val header: String? = req.headers["Accept"]    val length = header?.let { it.length } ?: 0     // run / with: compute a result from an object's members    val summary = with(req) { "$url (${timeoutMs}ms, ${headers.size} headers)" }     println(ids)                    // [1, 2, 3]    println(length)                 // 16    println(summary)                // https://example.com (5000ms, 1 headers)}

When they hurt

  • Nesting. Inside a.apply { b.let { ... } }, this is a and it is b; one more level and nobody can tell which this is meant. Name the parameter, or use a local val.
  • ?.let { } ?: ... as if-else. The Elvis also fires when the block itself returns null, which is easy to miss. For two real branches, write if (x != null) ... else ... (smart casts make it just as short).
  • Shadowing. In apply, a property of the object hides a local variable or a property of the enclosing class with the same name. name = name inside apply assigns the object's property to itself.
  • Long blocks. A 30-line run { } is a function without a name. Give it one.
  • let just to call one function. x.let { foo(it) } is foo(x), unless x is nullable.

takeIf { } and takeUnless { } are the close relatives: input.takeIf { it.isNotBlank() } returns the object or null, and pairs well with ?.let.

The Kotlin documentation gives the same advice: choose by purpose, avoid nesting, take care when chaining, and if the block grows, prefer plain code. Like most style rules, the goal is that the next reader sees the intent without counting receivers.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.What is the type of val x = StringBuilder().apply { append("hi") }?
  2. 2.Which is the idiomatic way to run code only when a nullable value is not null, using it as a non-null value?
  3. 3.user?.let { save(it) } ?: showError(). When can showError() run even though user is not null?

0 of 3 right so far. Get all three to finish this topic.