25

Any, Unit, Nothing and all their friends - ITNEXT

 2 years ago
source link: https://itnext.io/any-unit-nothing-and-all-their-friends-e39613b48235?source=friends_link&gi=9aefa7823408
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Any, Unit, Nothing and all their friends

Image for post
Image for post

This is an easy one, Any is just the “root” type, so every other type extends from it. It is like Object in Java, in fact, the compiled code for a value of type Any is an Object.

// Kotlin

val greeting: Any = "Hi there!"

// Java

private final Object greeting = "Hi there!";

A function returning Unit in Kotlin means returning void in Java. Additionally, a function returning Unit in Kotlin doesn’t explicitly need to return it. So these two functions compile to the same:

// Kotlin

fun returnsUnit(): Unit {
}fun returnsUnitExplicitly(): Unit {
return Unit
}

// Java

public final void returnsUnit() {
}

public final void returnsUnitExplicitly() {
}

What is Unit then?

Unit is defined as a singleton instance. For this reason there is a single valid value for the type:

val unit: Unit = Unit

From the type hierarchy perspective, similarly to any other type, Unit is a child of Any:

val unit: Any = Unit

Nothing

Nothing is a type without any possible value. This is because it is defined as a regular class with a private constructor. As per the documentation: Nothing has no instances. You can use Nothing to represent “a value that never exists”.

So if there is no way to construct or get any value of type Nothing, how is it useful?

Typing functions that never return (or throw exceptions)

fun infiniteLoop(): Nothing {
while (true) {
println("Hi there!")
}
}
fun throwException(): Nothing {
throw IllegalStateException()
}

The compiler is smart enough to infer in both cases that the functions will never return (properly). That is why any code placed after a call to a function returning Nothing will be ignored. Compiler will show a warning for unreachable code.

Let’s define a new function that may throw an exception (or return null):

fun mayThrowAnException(throwException: Boolean): Nothing? {
return if (throwException) {
throw IllegalStateException()
} else {
println("Exception not thrown :)")
null
}
}

If we call this from our main:

fun main() {
val result = mayThrowAnException(true)
if (result == null) { // Always true
println("Ignored code")
}
}

The compiler hints us that the result will always be null. Why is this? Because the only way for the program to continue after calling mayThrowAnException function is to return a null (because it would be Nothing otherwise). If the function returns Nothing is because it will throw an exception, which would lead to our program breaking.

Converting throw and return into expressions

Have you ever though why this is possible?

val nullableValue: String? = null
val value = nullableString ?: throw IllegalStateException()

nullableString is of type String?, and throw IllegalStateException() is Nothing. It happens that Nothing is a subtype of every type. This is the reason for that expression to finally evaluate to a String.

Another example:

val nullableValue: String? = null
val value: Int = nullableValue?.toInt() ?: return

This case is similar to the previous one, but instead of throwing an exception, we just return from the functions. The type of return is Nothing, so same as above.

In the end, the language “converts” both return and throw into expressions (whose type is Nothing), which leads to a more concise programming (rather than being a “exceptional” statements).

Where is Nothing in the type hierarchy?

We said at the beginning of the article that Any is on top of the type system. Nothing is the opposite, is on the bottom. What does this mean? It means that Nothing is a subtype of every type. This is why the code examples shown above are valid. Let’s review the last part of the previous example:

nullableValue?.toInt() ?: return

nullableValue?.toInt is of type Int? and then we use the elvis operator to return from the function in case the nullableValue is null. And the type of return expression is Nothing.

So mapping to types, we would have:

Int? ?: Nothing

The return type of the whole expression is the type that both types have in common. Because Nothing is a subtype of every type, Int? is what they have in common. As a generalization, the type in common between a type T and Nothing will always be T.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK