Kotlin has three types that are very similar in nature:
Void
Unit
Nothing
It almost seems like they're making the JavaScript mistake:
null
undefined
void(0)
Assuming that they haven't fallen into the same mistake, what are they all for, and how do they differ?
Like void, Unit is the return type of any function that does not return any meaningful value, and it is optional to mention the Unit as the return type. But unlike void, Unit is a real class (Singleton) with only one instance.
UNIT actually contains valuable information, it basically just means "DONE". It just returns the information to the caller, that the method has been finished. This is a real piece of information so it can be seen as the return value of a method.
Nothing has no instances. You can use Nothing to represent "a value that never exists": for example, if a function has the return type of Nothing, it means that it never returns (always throws an exception).
The Unit type in Kotlin is the equivalent to the void type in Java. Or, if you prefer, is the result value of any statement (for example println() ). 3.
The Void
type is from Java. You generally won't use this from Kotlin unless you're using some Java-library that uses it.
The Unit
type is what you return from a function that doesn't return anything of interest. Such a function is usually performing some kind of side effect. The unit type has only one possible value, which is the Unit
object. You use Unit
as a return type in Kotlin when you would use void
(lowercase v) in Java.
The Nothing
type has no values. If a function has return type Nothing
, then it cannot return normally. It either has to throw an exception, or enter an infinite loop. Code that follows a call to a function with return type Nothing
will be marked as unreachable by the Kotlin compiler.
Because Nothing
has no values, Nothing?
is actually the type that captures only the null
value in Kotlin.
Unit
Unit
is like void
In Kotlin, when a function does not return any meaningful value, it is declared to return Unit
, just like void
in Java:
fun greet(): Unit { println("Good day!") }
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 greet() { println("Good day!") }
Unit
is a Singleton
The Unit
is a class with only a single object (singleton pattern) and that object is the Unit
itself. It is declared in the kotlin
package using an object declaration as shown below:
public object Unit {
override fun toString() = "kotlin.Unit"
}
Unit
in Functional Programming
Kotlin has first-class support for functional programming. It's common to have a Unit
in a functional programming language. It makes the function types more readable by enabling all the functions to be declared as having a return value, even when a function does not return a value:
val greet: () -> Unit = { println("Good day!") }
Here, () -> Unit
is a function type and the Unit
after the ->
indicates that this function type does not return any meaningful value. Mentioning the Unit
cannot be skipped in function types.
Unit
for Extending Generics
Every function has to return a value. Kotlin decided to represent this with a class rather than with a special type void
as in Java. The reason for using a class is that the type system can be made more consistent by making it a part of the type hierarchy.
For example, let's say we have a generic interface
called Worker<T>
that performs some work. The doWork()
function of this interface does some work and has to return a value T
:
interface Worker<T> {
fun doWork(): T
}
But sometimes, we might want to use this interface for some work where we don't need to return any value, for example, the work of logging, in the LogWorker
class shown below that extends the Worker
interface:
class LogWorker : Worker<Unit> {
override fun doWork() {
// Do the logging
}
}
This is the magic of Unit
where we are able to use the pre-existing interface that was originally designed to return a value. Here we make the doWork()
function return the Unit
value to serve our purpose in which we don't have anything to return. So, it's useful when you override a function that returns a generic parameter.
Notice that we have also skipped mentioning Unit
return type for the doWork()
function. There's no need to write a return
statement either.
Nothing
Nothing
's Value Never Exists
In Kotlin, the class Nothing
represents a value that never exists. There can never be any value/object of this class because its constructor
is kept private
. It's defined in the kotlin
package as follows:
public class Nothing private constructor()
Nothing
is used for the return type of a function that never returns a value. For example, a function with an infinite loop or a function that always throws an exception. The error()
function from Kotlin standard library is an example that always throws an exception and returns Nothing
. Here is the code for it:
fun error(message: Any): Nothing = throw IllegalStateException(message.toString())
Nothing
is the Bottom Type
In type theory, the type that has no values is called a bottom type and it is a subtype of all other types. So, Nothing
is the subtype of all types in Kotlin, just like Any?
is the supertype of all types. So, the value(that never exists) of type Nothing
is assignable to the variables of all types, for example:
val user: User = request.user ?: error("User not found")
Here, we are calling the error()
function that we defined earlier, if the user
is null
, using the elvis operator(?:
). The error()
function returns the value of type Nothing
but it can be assigned to the variable of type User
because Nothing
is a subtype of User
, just like it is a subtype of any other type. The compiler allows this because it knows that the error()
function will never return a value, so there is no harm.
Similarly, you can return Nothing
from a function that has any other return type:
fun getUser(request: Request): User {
return request.user ?: error("User not found")
}
Here, even though the getUser()
function is declared to return a User
, it may return Nothing
, if the user
is null
.
Nothing
in Null Object Pattern
Consider the following example of a function that deletes the files given in a list:
fun deleteFiles(files: List<File>? = null) {
if (files != null) files.forEach { it.delete() }
}
The problem with the design of this function is that it doesn't convey whether the List<File>
is empty or null
or has elements. Also, we need to check whether the list is null
before using it.
To solve this problem, we use the null object design pattern. In null object pattern, instead of using a null
reference to convey the absence of an object, we use an object which implements the expected interface, but leaves the method body empty.
So, we define the object of the interface List<Nothing>
:
// This function is already defined in the Kotlin standard library
fun emptyList() = object : List<Nothing> {
override fun iterator(): Iterator<Nothing> = EmptyIterator
...
}
Now we use this null object in our deleteFiles()
function as a default value of our parameter:
fun deleteFiles(files: List<File> = emptyList()) {
files.forEach { it.delete() }
}
This removes the uncertainty of null
or empty and makes the intent clearer. It also removes the null checks because the functions on the null object are empty, they will be called but they are no-ops (no operation in them, so they will do nothing).
Nothing
for Covariant Generics
In the example above, the compiler allows us to pass List<Nothing>
where List<File>
is expected. This is because the List
interface in Kotlin is covariant since it's defined using the out
keyword, that is, List<out T>
. And as we learnt, Nothing
is a subtype of all types, Nothing
is a subtype of File
too. And due to covariance, List<Nothing>
is a subtype of List<File>
, List<Int>
, List<User>
and so on... List<AllTypes>
. This applies to any type with the covariant generics(out
), not just List
.
Nothing
for Better Performance
Just like the function emptyList()
used in our example, there are predefined functions like emptyMap()
, emptySet()
, emptySequence()
that return null objects. All these are defined using Nothing
. You can define your own objects like this.
The advantage here is that these return singleton objects, for example, you can call the same emptyList()
function for getting an empty instance, whether it is for assigning to List<File>
, List<Int>
and ... List<AllTypes>
and in multiple places. Since the same object is returned every time, it saves the cost of object creation and memory allocation.
Void
Void
for Extending Generics in Java
The Void
class is from the java.lang
package while the Unit
and Nothing
are from the kotlin
package. Void
is not intended to be used in Kotlin. Kotlin has its own class in the form of Unit
.
Void
is used in Java for extending generic interfaces like our Worker
interface example written for Unit
where we have to return a value. So for converting our Kotlin code to Java, we can use Void
the same way we have used Unit
for our Worker
example and rewrite the code in Java as follows:
interface Worker<T> {
T doWork();
}
class LogWorker implements Worker<Void> {
@Override public Void doWork() {
// Do the logging
return null;
}
}
Notice that when using Void
, we have to use Void
as a return type(can't skip) as well as need to write the return
statement whereas for Unit
we can skip both. This is another reason to avoid using Void
in Kotlin code.
Conclusion
So, Unit
and Nothing
are not a mistake by Kotlin designers in my opinion and are not as questionable as null
, undefined
and void(0)
in Javascript. Unit
and Nothing
make the functional programming a breeze while providing other useful features mentioned. They are common in other functional programming languages too.
That's it! Hope that helps.
Void
is uninstantiable type. It is a plain Java class and has no special meaning in Kotlin.
Unit
type has only one value. Replaced Java void
(notice: not Void
). More info in Kotlin docs.
Nothing
has no instances (just like Void
). It represents "a value that never exists". In Kotlin if you throw an error it is a Nothing
(see Kotlin docs).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With