Kotlin defines a few of extension functions like
apply() in its Standard.kt file. You probably have seen some of them in various tutorials or even used them already. Sometimes you may also wonder which one to use. In this post, I will walk through those confusing functions and see if we can understand the differences.
There are four (plus one added in 1.1) functions that look similar, and they are defined as follow:
If you try to read the comments, it’s very difficult to understand the differences immediately. Instead let’s read through the method signatures “literally” and look for things that are the same and ones that are different.
Things that are the same:
- T is the type of instance you want to operate with, and in Kotlin’s terminology it is called receiver type. Even though
with()is not an extension method, the instance of T is still called receiver in method definition.
- The last argument is a function that you can supply to operate the receiver object. Since it’s the last argument, so Kotlin allows you move the function declaration out of the method parenthesis.
Things that are different:
also()return T, but others return R.
- block in
let()is is a function that takes in T as the argument, but in others it is an extension function on type T.
Show me some code
Nothing is better than real code snippets, right?
Let’s start with Java version, so we can better understand how those Kotlin functions can help us write more concise and readable code at the same time as well.
See the equivalent implementations and compare the functions
also() vs apply()
Let’s compare these two functions first:
- their return value is always
this, which is the receiver instance with type T.
Unitin both functions.
The same implementation:
the print() method is defined as for brevity:
Both functions work exactly the same way, but with one subtle difference that in
also() we have to use an explicit
it variable to append content. In
apply() we can directly call append() as if
block was part of the instance.
block is defined as
(T) -> Unit, but it is defined as
T.() -> Unit in
So you can simply see
apply() as a “simpler” version of
also() that it has an implicit this defined to be used in the function body.
also() requires you to use
it, but it can be more readable in some cases, or you can even name the instance to fit your context better.
let() vs run()
blockis defined as
(T) -> Rin
let(), but it is defined as
T.() -> Rin
- their return value is
Similar to the previous comparison,
let() requires an explicit it and
run() has an implicit this in their
However, this pair of functions has another major difference than
apply(). They return the value returned by the
block body. In other words, both
run() return whatever
See the following sample:
The first one returns the
enclosing string builder since
append() returns the string builder itself, but the second one returns
length() returns an integer.
run() can be used when you want to operate an instance of T but you also want to have a different return value R.
with() is not an extension function on type T, but it takes a instance of T as the first argument. Also Kotlin allows us to move the last function argument out of the parenthesis.
block is defined as
T.() -> R so you don’t have to use
it, and you can change the return value in
I made a simple comparison chart to include the characteristics and hopefully we can see the differences more easily:
|Name||Is Extension Function?||Return Type||Argument in |
|also()||yes||T (this)||explicit it||(T) -> Unit|
|apply()||yes||T (this)||implicit this||T.() -> Unit|
|let()||yes||R (from block body)||explicit it||(T) -> R|
|run()||yes||R (from block body)||implicit this||T.() -> R|
|with()||no||R (from block body)||implicit this||T.() -> R|
These functions looks very similar and can be interchangable in some use cases. For better consistency and readability for Kotlin beginners, I would suggest that maybe start with
let() since they require developers to name the argument or use the explicit
it in the block body. It may be slightly verbose than other functions, but it is always good to have some common ground among your team members when starting using Kotlin.