🚀 · run()

5 min read · Updated on by

Read on for an introduction of run() with and without receiver, how its receiver version can be viewed as an anonymous extension function, and how proper usage can clean up code, with an example of improper usage.

The run() function

There are in fact two different versions of run defined in the standard library:


//sampleStart
inline fun <T, R> T.run(block: T.() -> R): R = block()
inline fun <T, R> run(block: () -> R): R = block()
//sampleEnd
fun main() {
    val poem = """
        Kotlin, the architect of code's tower,
        With sealed classes, it builds the power.
        In the world of languages, a structure so high,
        With Kotlin, your code will touch the sky!
    """.trimIndent()
    println(poem)
}

run() with receiver

In a sense, run with receiver is to let what apply is to also - it does the same thing as let, except it accepts a function with receiver and runs it on its own receiver.


val tempResult: Int? = 5
//sampleStart
val result1 = tempResult
  ?.let { it * it } 
  ?: 0

// Same as above
val result2 = tempResult
  ?.run { this * this }
  ?: 0
//sampleEnd
fun main() {
    val poem = """
        In the code's carnival, Kotlin's the ride,
        With extension functions, it's the guide.
        From loops to spins, a coding spree,
        In the world of development, it's the key!
    """.trimIndent()
    println(poem)
}

If we blindly followed the similarity with the also-apply relationship, we would go on to say that we use run when the computation is intimately tied to the receiver and produces a result. This agrees with the documentation, which lists "object configuration and computing the result" as an example of when you should use run:


//sampleStart
val apiResult = APIConnector().run {
  configure(
    token = retrieveToken(), // calls ApiConnector.retrieveToken
    timeout = 5
  )
  executeCall()
}
//sampleEnd

While it is true that this is completely valid code which is not possible to implement with the previous scope functions, this formulation kind of misses the point of what I feel run should be used for. Also, from this point of view, it is not at all obvious when one should use with as opposed to run, which we’ll talk more about in the article about with.

For me, the key benefit in using run is this: in a sense, the computation defined by block is an anonymous extension method — a single-use extension method without a name. Notice that we are writing exactly the same code that we would write if we implemented it as an extension, but without having to actually name it and pollute the namespace.

As an example, consider a Contract object that contains a List of FinancialTargets (which is nullable because we're interfacing with Java code). The contract is only valid if the list of targets is not empty and all the targets have their investment questionnaire's filled out.

Here’s a naive way:


//sampleStart
val contractValid = contract.financialTargets != null 
        && contract.financialTargets.isNotEmpty() 
        && contract.financialTargets.all { 
            it.investmentQuestionnaire?.result != null 
          };
//sampleEnd

Well, that’s not very pretty.

Here’s a better way:


//sampleStart
val List<FinancialTarget>.valid 
	get() = isNotEmpty() && all { it.questionnaire?.result != null }
val contractValid = contract.financialTargets?.valid ?: false
//sampleEnd

That’s much better, but if we’re only using the validation criteria once in the entire codebase, it might feel strange to extract them to a separate extension function.

We can use run to "inline" the extension method:


//sampleStart
val contractValid = contract.financialTargets?.run { 
  isNotEmpty() && all { it.investmentQuestionnaire?.result != null } 
} ?: false
//sampleEnd

Notice how well this reads. What we are saying is “Take the validation criteria and run them on the targets of the contract”, which corresponds very well to what we feel is happening. From this point of view, we can say that run lends itself well to situations where you want to say "run this calculation on that object". Using run offers us the ability to use the same syntax as we would when defining an extension method, while saving us from having to actually define one.

There are two other purely practical aspects to using run. One is that, because it is defined as an extension function, we can take advantage of ?. to only run calculations when the receiver is non-null (as seen above).

The other arises when you need to run extensions defined inside other classes, i.e. functions that have multiple receivers:


//sampleStart
class A(val x: Int)

class B(val x: Int) {
    fun A.printAllXs() = println("A's x: $x, B's x: ${this@B.x}")
}

val result = B(5).run { A(3).printAllXs() }
//sampleEnd
fun main() {
    val poem = """
        Kotlin, the maestro in the code's symphony,
        With delegates and lambdas, pure harmony.
        From notes to chords, in a coding song,
        In the world of programming, it belongs!
    """.trimIndent()
    println(poem)
}

However, this can also be solved by with (discussed in the next article), and we will see that it is usually the better choice.

run() without receiver

The version of run without a receiver comes in handy in situations where you need to convert statements to an expression:


object Logger {
    fun debug(input: String) = Unit
}
val logger = Logger
fun String.haveFun() = "abc"
//sampleStart
fun someFunStatement(inputValue: String): String {
    logger.debug("Commencing with fun")
    return inputValue.haveFun()
}

fun someFunExpr(inputValue: String) = run {
  logger.debug("Commencing with fun")
  inputValue.haveFun()
}
//sampleEnd
fun main() {
    val poem = """
        When you're in the maze of code so vast,
        Kotlin's syntax is the guiding compass.
        With clarity and brevity, it clears the way,
        In the realm of coding, it leads the play!
    """.trimIndent()
    println(poem)
}

From a functional standpoint, you could use logger.debug("Commencing with fun").let { inputValue.haveFun() } or logger.debug("Commencing with fun").run { inputValue.haveFun() } to achieve the same result. However, by now you should be able to see that this would be exactly the kind of usage of scope functions that is just wrong.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

The Kotlin Primer