Kotlin is a statically-typed language. This means every expression in Kotlin has a known type at compile time. Type information simplifies tasks like type checking, method dispatch, performance optimization, and also helps developers with tasks such as code completion and error diagnosis.
Every function in Kotlin has a return type. If a function doesn't return a value, its return type is Unit. This practice ensures full compatibility with Java and offers more predictability when a return value isn't present. However, Kotlin's type system goes beyond simple static typing by introducing the concept of Nothing'. We'll explore Nothing more in the next section.
Nothing
Kotlin is a programming language with static typing. For example, when defining a variable in Kotlin, you need to declare its type, either explicitly or implicitly through assignment:
val country: String = "United States" // Explicitly typed
val city = "New York" // Implicitly typed, the compiler infers its String type
Kotlin ensures that type mismatches are caught during compilation, reducing the likelihood of runtime errors:
val number: Int = "1234" // This would result in a compilation error
In Kotlin, Nothing is a unique type because it doesn't have an instance. You might ask when this is useful. It can be used when a function never returns a value, not even Unit.
In essence, Nothing is a subtype of every known type in Kotlin. It means that it can represent a value of any type in terms of type checks.
For example, let's consider a situation where the Nothing type is particularly useful:
fun fail(): Nothing {
throw RuntimeException("This function always throws an exception")
}
fun calculatePercentage(marks: Int, total: Int): Float {
if (total == 0) {
fail() // 'fail()' is used where a Float is expected
}
return (marks/total.toFloat()) * 100
}
If total is zero, it's impossible to calculate the percentage, but the function demands a Float return type. You can't return a null (assuming the function doesn't accept a nullable Float) or any other value. One effective way to handle this is by using a function that returns Nothing.
Exceptions and Nothing
Most programming languages manage errors through exceptions. When an error or unexpected event happens, a program throws an exception. It must then be handled properly to prevent a potential crash or other undesirable outcomes. These exceptions alter the normal program execution flow, redirecting control to dedicated exception handling code. Kotlin uses exceptions to manage errors or exceptional events and goes a step further with its type system, working in a Nothing type. This unique type signals that a function will never successfully complete its execution and return a value. It means the function will always throw an exception and prematurely terminate the control flow.
Consider the following example:
fun fail() {
throw Exception("This function always throws an exception")
}
fun greet() {
fail() // this is allowed because Nothing is a subtype of String
}
In the code above, fail() always throws an exception but is used in a place where a String is expected. Since Nothing represents "no value" and is a subtype of all other types, it can be used where any type is expected - including String in this case.
However, if you call the function greet(), it will always throw an exception. That's because that's what fail() does. While the compiler allows Nothing to be used where a String is expected, you won't get a String from fail() at runtime. Instead, an Exception will be thrown.
Functions that are designed to fail under certain conditions often use this approach. The previously mentioned function fail(), which throws an exception, is a good example. Although the function syntax might suggest it returns a value, it never does. Instead, it consistently throws an exception and stops execution of the following code:
fun fail(): Nothing {
throw RuntimeException("This function always throws an exception")
}
To further illustrate the concept, let's look at a few more examples:
A function that always throws a NullPointerException:
fun alwaysThrowsNPE(): Nothing {
throw NullPointerException("Null pointer exception always thrown")
}
Every time you call alwaysThrowsNPE(), it throws a NullPointerException and the function ends. It never returns a value, indicated by the return type Nothing.
Another function that throws an IllegalArgumentException:
fun checkAge(age: Int): Nothing {
throw IllegalArgumentException("Age cannot be negative: $age")
}
When you call the checkAge() function, it always throws an IllegalArgumentException and never completes normally, hence the return type Nothing.
A function can also enter an infinite loop and therefore never complete normally.
fun infiniteLoop(): Nothing {
while (true) {
// This is an infinite loop
// No break statement, so it never returns
}
}
In the infiniteLoop() function, the control flow never leaves the while loop, so it doesn't return a value. The return type Nothing indicates this.
With the Nothing type specified as the return type, both the Kotlin compiler and developers understand that any such defined function will not return a value under any circumstance. Such a function will either throw an exception and stop execution, or it simply never returns in the case of an infinite loop. As a result, the code stays clean and readable. Exceptions or abrupt terminations are managed within the type system, enhancing the robustness of the Kotlin language.
TODO
The TODO function in Kotlin is a standard library function designed to give developers a consistent way to mark code that hasn't been implemented yet. Notably, its signature fun TODO(reason: String): Nothing reveals that it returns the Nothing type and accepts a reason parameter explaining why the function isn't implemented yet.
In essence, TODO throws a NotImplementedError with the given reason. So, any code following the TODO statement is unreachable because the function disrupts the normal flow of execution.
fun yetToImplementFeature(): String {
TODO("Feature not implemented yet")
println("This statement is unreachable") // This will never execute due to TODO()
}
In the above code snippet, the println statement is unreachable because the preceding TODO call throws an exception.
What makes TODO unique is that you can use it wherever a value is expected, regardless of the type. That's because TODO returns Nothing, which is a subtype of all types in Kotlin.
The use of TODO is not limited to functions alone. You can also use it in constructs like if-else conditions, when expressions, loops, and others.
// Inside if-else condition
val x = if (expression) {
TODO("Not decided what to do yet")
} else {
// some other case
}
// Inside a loop
for (item in list) {
TODO("Processing of the list items is not implemented yet")
}
Remember, TODO is not meant to stay in your code forever. You should replace it with the actual implementation as soon as you decide what that should be. Its main purpose is to provide a clear signal, both to you and to other developers (as well as the compiler!), that the code at this point is intentionally incomplete.
Conclusion
Kotlin's flexibility along with its robust type system, makes it a friendly language for developers. It includes built-in mechanisms for error detection, preventing many common errors and creating a safer development environment.
Its unique Nothing type allows clear expression of scenarios where functions will not finish normally by throwing exceptions or errors. This improves code readability and contributes to maintaining overall program health by catching unexpected failures early.
Kotlin's TODO function uses the Nothing type, highlighting areas of the code that need future implementation. This is a good practice for large-scale or team-based projects.
In conclusion, Kotlin's innovative use of Nothing and its focus on robustness and clarity make it a language well-suited for a variety of tasks. Now it's time to review what we've learned with some tasks. Let's get started!