Scope Functions Deep Dive
In this page:
val r1 = receiver.let { it -> result } // returns the block result
val r2 = receiver.run { this -> result }
val r3 = receiver.also { it -> } // returns receiver
val r4 = receiver.apply { this -> } // returns receiver
val r5 = with(receiver) { this -> result }
Comparing Return Values
apply and also always return the original receiver object, while let, run, and with return whatever the lambda block itself produces as its last expression.
Example: Comparing Return Values
// Entry point: execution of the program starts here
fun main() {
// Declare a read-only variable `listApply`, set to `mutableListOf(1, 2).apply { add(3) }`
val listApply = mutableListOf(1, 2).apply { add(3) }
// Declare a read-only variable `sizeRun`, set to `mutableListOf(1, 2).run { add(3); size }`
val sizeRun = mutableListOf(1, 2).run { add(3); size }
// Print "apply result: $listApply" to the console, with a trailing newline
println("apply result: $listApply")
// Print "run result: $sizeRun" to the console, with a trailing newline
println("run result: $sizeRun")
}
Login to try C/C++/Java/PHP code in the editor
Comparing this vs it
apply, run, and with make the receiver available as this (often omitted), while let and also make it available as it, an explicit parameter name.
Example: Comparing this vs it
data class Box(var content: String)
fun main() {
val box = Box("empty")
box.apply { content = "full" } // this.content
box.also { println("Box now has: ${it.content}") } // it.content
}
Login to try C/C++/Java/PHP code in the editor
Choosing let for Transformation
let is ideal when you want to transform a value into something else and get that new result back, especially in combination with a safe call on a nullable value.
Example: Choosing let for Transformation
// Entry point: execution of the program starts here
fun main() {
// Declare a read-only variable `name` (a `String?`), set to `"kotlin"`
val name: String? = "kotlin"
// Declare a read-only variable `length`, set to `name?.let { it.length }`
val length = name?.let { it.length }
// Print "Length: $length" to the console, with a trailing newline
println("Length: $length")
}
Login to try C/C++/Java/PHP code in the editor
Choosing apply for Configuration
apply is ideal when configuring an object's properties and you want the fully configured object itself back at the end, without needing a separate computed result.
Example: Choosing apply for Configuration
// Define a class `Settings`
class Settings {
// Declare a mutable variable `darkMode`, initialized to `false`
var darkMode = false
// Declare a mutable variable `fontSize`, initialized to `12`
var fontSize = 12
}
// Entry point: execution of the program starts here
fun main() {
// Call `Settings().apply` with a trailing lambda, storing the result in `settings`
val settings = Settings().apply {
darkMode = true
fontSize = 16
}
// Print "Dark mode: ${settings.darkMode}, Font size: ${settings.fontSize}" to the console, with a trailing newline
println("Dark mode: ${settings.darkMode}, Font size: ${settings.fontSize}")
}
Login to try C/C++/Java/PHP code in the editor
- Choosing the wrong scope function for the job, such as using
applywhen the intended result was the block's own computed value (which needsrunorletinstead). - Nesting scope functions deeply, making it hard to tell which
this/itbelongs to which block. - Using a scope function purely for style when a plain statement would be clearer and just as short.
let,run,with,apply, andalsoall execute a block against a receiver but differ in what they return and whether the receiver isthisorit.applyandalsoreturn the original receiver;let,run, andwithreturn the block's result.apply,run, andwithexpose the receiver asthis;letandalsoexpose it asit.- Choosing the right scope function comes down to what you need back: the original object, or a transformed result.
Chapter Quiz — Complete all 6 topics to unlock
0/6 topics done
Complete these topics first: