In this part of the Kotlin Programming Series, I will talk more about the concept of Nullable types (Optionals), nullable type operators, the concept of Unit, Nothing, TODO(), and scope functions like let, run, with, apply, also.
Nullable Types (Optionals)
In Kotlin, there is a concept of types that can hold null(nullable references) and that cannot hold null (non nullable references). a regular variable of type String can not hold null.
var str: String = "abc"
str = null // compilation error
This helps to freely access the non nullable types without null check and without fear of causing Null Pointer Exception.
To declare a variable of nullable type The format is <type> ?. To declare a nullable String, we can declare it as String?
var nullableStr: String? = "abc" // note the ?
nullableStr = null // ok
Accessing a property on a nullable type directly would not be safe, and the compiler will report an error
val len = nullableStr.length //compilation error: variable ' nullableStr ' can be null
So, there are alternate ways to do that:-
- Checking for null in conditions
First way is to explicitly check if string is null, and handle the two options separately:
val length = if (nullableStr != null) nullableStr.length
else -1
- The other way is to access it using Nullable Type Operators
Nullable Type Operators
Safe Call Operator (?.)
The safe call operator when used with a nullable variable nullableStr
val len = nullableStr?.length
returns nullableStr.length if nullableStr is not null, and null otherwise.
This operator can be chained
When chained and used in RHS, like
var val = bob?.department?.head?.name
If any of bob, department or head is null, it will return null, without causing NPE
When chained and use in LHS
bob?.department?.head?.name= managersPool.getManager().name
If either ‘bob’ or ‘department’ or ‘head’ is null, RHS is not executed
To perform a certain operation only for non-null values, you can use the safe call operator together wit let :-
for (item in listWithNulls) {
item?.let { println(it) } // prints Kotlin and ignores null
}
The above prints all values of item from list for which item is not null
Elvis Operator(?:)
When we have a nullable reference nullableStr, we can say “if nullableStr is not null, use it, otherwise use some non-null value”:
val l: Int = if (nullableStr!= null) nullableStr.length else -1
We can rewrite this using elvis operator
val l = nullableStr?.length ?: -1
If the expression to the left of ?: is not null, the elvis operator returns it, otherwise it returns the expression to the right.
Note : The right-hand side expression is evaluated only if the left-hand side is null.
Not Null Assertion Operator (!!)
The not-null assertion operator (!!) converts any value to a non-null type and throws an exception if the value is null. We can write nullableStr(!!), and this will return a non-null value of nullableStr (e.g., a String in our example) or throw an NPE if nullableStr is null.
val length = nullableStr!!.length
We should use this operator to access value when we are certain that the value for the variable will be not null.
The special types:- Unit and Nothing
Unit
Unit is exactly equivalent to the void type in Java. Any function that has Unit as return type does not return anything. We can explicitly add Unit as the return type for functions returning void.
fun doSomething() : Unit {
//do something here
}
Such a function usually performs some kind of side effects.
It’s a convention to skip writing Unit when a function returns Unit because Unit is considered the default return type by the compiler:
fun doSomething(){
//do something here
}
Nothing
Nothing is a Class (not an Object) and is a subclass of any other class, including the final ones. There is a catch, though: it is impossible to instantiate a Nothing object because it has a private constructor.
Its declaration is pretty simple:
public class Nothing private constructor ()
Since it’s not possible to pass a value of type Nothing, it represents the result of “a function that never returns.” For example, because it throws an exception or because it has an infinite loop. Code that follows a call to a function with return type Nothing will be marked as unreachable by the Kotlin compiler.
TODO()
TODO() is a smarter way of throwing NotImplementedError for methods of a class that are not implemented. When the code reaches this block it throws.
TODO() has two different signatures:-
fun TODO(): Nothing
Always throws NotImplementedError stating that operation is not implemented.
fun TODO(reason: String): Nothing
reason :- a string explaining why the implementation is missing.
Scoping Functions
Before we jump into the world of various scoping functions, it would make sense to discuss the two characteristics of scoping functions that are the differentiating factors among them
a. This vs. it argument (how the argument is passed)
Scoping functions may do similar operation except for the way they pass themselves as the argument.
If the scoping function is an extension function, like run, the object on which the function is called could be referred to as “this” and “this” is implicitly passed as an argument. Therefore, for accessing its property say length. we could use $(this.length) or $length.
nullableStr?.run {
println("The length of this String is $length")
}
A scoping function may also pass itself as “it” argument , like in “let”. So, to access the property length, we may write it as :-
nullableStr?.let {
println("The length of this String is ${it.length}")
}
b. Return Value
The second differentiating factor is the return value:-
The result returned by the function can be one of two things:
- The receiver object itself.
- Whatever the code block returns.
Let’s look at an example of apply() function
We need to know the day of the year.
val date: Int = Calendar.getInstance().apply {
set(Calendar.YEAR, 2016)
set(Calendar.MONTH, Calendar.FEBRUARY)
set(Calendar.DAY_OF_MONTH, 15)
}.get(Calendar.DAY_OF_YEAR)
As you can see, the result of calling apply() is a Calendar – it’s the same instance as the receiver object that we called apply() on. So apply() fits in the first category – it returns the receiver object. Then we apply get() to get the day of the year
But with run(), the result returned is the result of the code block, i.e. the second case. so we’d add the call to get() inside run:
val date: Int = Calendar.getInstance().run {
set(Calendar.YEAR, 2016)
set(Calendar.MONTH, Calendar.FEBRUARY)
set(Calendar.DAY_OF_MONTH, 15)
get(Calendar.DAY_OF_YEAR)
}
let() function
let gets “it”, the receiver parameter and the last statement in the let block is returned as a result of let.
Use the let() function in either of the following cases:
- execute code if a given value is not null
getNullablePerson()?.let {
// only executed when not-null
promote(it)
}
also( ) function
Use the also() function, if your block does not access its receiver parameter at all, or if it does not mutate its receiver parameter ”it”. It returns the object on which it is called as the return value.
val author = author.also {
requireNotNull(it.age)
print(it.name)
}
Don’t use also() if your block needs to return a different value. For example, this is very handy when executing some side effects on an object or validating its data before assigning it to a property:
with() function
with() receives “this” as implicit parameter and the return is Unit.
Use with() only on non-nullable receivers, and when you don’t need its result. For example:
val person: Person = getPerson()
with(person) {
print(name)
print(age)
}
run() function
run() receives “this” as parameter and returns the output of last line of block as the return value
Use run() function if you need to compute some value or want to limit the scope of multiple local variables.
val inserted: Boolean = run {
val person: Person = getPerson()
val personDao: PersonDao = getPersonDao()
personDao.insert(person)
}
Use run() also if you want to convert explicit parameters to implicit receiver.
fun printAge(person: Person) = person.run {
print(age)
}
apply( ) function
Use the apply() function if you are not accessing any functions of the receiver within your block, and also want to return the same receiver. apply() receives “this” as implicit parameter and returns the object itself as return value. This is most often the case when initializing a new object. The following snippet shows an example:
val peter = Person().apply {
// only access properties in apply block!
name = "Peter"
age = 18
}
An image depicting which should be used when

Special Credits to the following authors from which code and concept excerpts have been taken:-
Elye :- https://medium.com/mobile-app-development-publication/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84
Dave Leeds :– https://typealias.com/guides/understanding-let-also-run-apply/
Faith Coskun:- https://medium.com/@fatihcoskun/kotlin-scoping-functions-apply-vs-with-let-also-run-816e4efb75f5
Leave a Reply