Scope functions.
Kotlin provides special functions for working with objects in a temporary scope. These are let, run, with, apply, and also. They seem similar but have subtle differences. Today we understand each one and learn when to use which.
Scope functions execute a block of code on an object.
They create a temporary scope where you work with the object. When the block ends, the scope ends.
All five scope functions do similar things. They differ in two ways: how you access the object inside, and what they return.
Some use this to access the object. Others use it. Some return the lambda result. Others return the object itself.
Understanding these differences helps you choose the right one.
let passes the object as an argument to the lambda.
val result equals value dot let open brace it dot doSomething close brace.
Inside the lambda, access the object using it or a named parameter.
let returns the lambda result. Whatever the lambda evaluates to, that is what let returns.
Common uses:
Null handling: value question mark dot let open brace use it safely close brace.
Transformations: value dot let open brace transform it close brace.
Scoping: limit a variable's visibility to the lambda.
let is great for processing a value and producing a new result.
run makes the object the receiver of the lambda.
val result equals value dot run open brace this dot doSomething close brace.
Inside the lambda, access the object using this, which can be implicit.
run returns the lambda result, like let.
Common uses:
Object configuration with computed result: val length equals str dot run open brace uppercase dot length close brace.
Running computations on an object.
run also exists without a receiver: run open brace computeSomething close brace. Just executes a block and returns the result.
Choose run when you want receiver access and need the lambda result.
with is like run but the object is passed as an argument, not called on.
val result equals with parenthesis value close parenthesis open brace this dot doSomething close brace.
It is not an extension function. You call with object open brace block close brace.
with returns the lambda result.
Common uses:
Working with an object when you do not need null safety.
Grouping calls on the same object: with config open brace setting1 equals x semicolon setting2 equals y close brace.
Avoid with on nullable objects. Use run instead so you get the question mark dot syntax.
apply makes the object the receiver, like run. But it returns the object itself.
val configured equals value dot apply open brace this dot property equals x close brace.
Inside, you configure the object. apply returns that same object after configuration.
Common uses:
Object initialization: val user equals User parentheses dot apply open brace name equals "Alice" semicolon age equals 30 close brace.
Builder pattern: build an object step by step, then use it.
apply is perfect when you want to configure an object and continue using it.
also passes the object as it, like let. But it returns the object itself, like apply.
val result equals value dot also open brace println "Value: " plus it close brace.
Inside, you do something with the object. also returns the object unchanged.
Common uses:
Logging: value dot also open brace log dot debug "Processing " plus it close brace.
Debugging: chain dot also open brace println "Debug: " plus it close brace dot next.
Side effects that do not change the object.
also is great for "also do this" without interrupting a chain.
Two questions guide your choice:
First: how do you want to access the object? If you want this (receiver), use run, with, or apply. If you want it (argument), use let or also.
Second: what do you want to return? If you need the lambda result, use let, run, or with. If you need the object back, use apply or also.
The combinations:
let: it access, returns result. Transform and produce new value.
also: it access, returns object. Side effects in a chain.
run: this access, returns result. Compute something from the object.
apply: this access, returns object. Configure the object.
with: this access, returns result. Like run, but not an extension.
Null safe processing with let: user question mark dot let open brace saveToDatabase it close brace.
Object configuration with apply: val button equals Button parentheses dot apply open brace text equals "Click" semicolon color equals Blue close brace.
Logging with also: data dot also open brace logger dot info "Received: " plus it close brace dot processData.
Computing with run: val reversed equals text dot run open brace filter open brace it dot isLetter close brace dot reversed close brace.
Grouping calls with with: with settings open brace volume equals 10 semicolon brightness equals 80 close brace.
Each scope function fits naturally into these patterns. Use the one that matches your intent.
Let us summarize.
Scope functions execute a block on an object.
let: it access, returns lambda result. For transformations.
run: this access, returns lambda result. For computations.
with: this access, returns lambda result. Non-extension form.
apply: this access, returns object. For configuration.
also: it access, returns object. For side effects.
Choose based on: how to access (this or it), what to return (result or object).