1. Overview
In this tutorial, we’re going to learn how to return multiple values from a Kotlin function. In the first two sections, we’ll briefly talk about tuples and destructuring declarations in Kotlin. Then we’ll use these declarations to return multiple values from a function.
2. No Tuples in Kotlin
Some programming languages such as C# and Scala have first-class support for tuples. Therefore, we can easily assign tuples to variables, pass them to functions, or even return them from functions.
On the other hand, Kotlin does not support creating arbitrary tuples, so we can’t return multiple values like:
// won't work
fun returnMultiple(): (Int, Int) {
return 1, 2
}
Even though we can’t return multiple values like the above example, we can mimic a limited tuple-like syntax on the call site. More specifically, we can write something like:
val (id, name) = returnIdAndName()
This tuple-like syntax is only applicable to assignments. So, we can’t use this trick when passing arguments to a function.
Now that we have a general idea about a possible solution, let’s see how the solution works.
3. Destructuring Declarations
In Kotlin, it’s possible to destructure an object into a set of variables. For instance, given the following data class:
data class User(val id: Int, val username: String)
We can destructure a variable of type User into its two properties in assignments:
val (id, name) = User(1, "Ali")
As shown above, instead of assigning the object instance to one variable, we’re assigning parts of it to different variables. That’s the essence of destructuring declarations: they’re creating multiple variables at once.
We can only use these types of declarations for types that are defining operator functions with the componentN naming convention. That is, the component1 function is the first destructured variable, the component2 function is the second variable, and so on.
In Kotlin, a few types are already usable for destructuring declarations:
- The data classes, as they declare component functions for all their properties
- Some built-in types such as Pair and Triple
- Collection types
So, basically, we can use all the above types to return multiple values from a function. In addition to that, we can define custom types with componentN functions and use them as return types as well.
4. Returning Multiple Variables
4.1. The Pair
Now that we know we can use destructuring patterns to return multiple values, let’s see a few examples for that. For starters, if we’re going to return two values of any type, we can return a Pair<T, R> from the function and destructure it on the call site:
fun twoPair(): Pair<String, Int> = "Ali" to 33 // equivalent to Pair("Ali", 33)
fun main() {
val (name, age) = twoPair()
println("$name is $age years old")
}
As shown above, even though we’re not returning multiple variables like a tuple in the function body, we’re treating them in the call site as they were tuples. That’s the magic of destructuring declarations.
When returning two values of any type, the Pair built-in type seems to be the best option.
4.2. The Triple
Quite similarly, we can return three values of any type with the Triple<T, R, Q> type:
fun threeValues(): Triple<String, Int, String> = Triple("Ali", 33, "Neka")
fun main() {
val (name, age, bornOn) = threeValues()
}
The Triple is also the best option to return three values of any kind.
4.3. Arrays and Collections
Moreover, we can use arrays and collections to return up to five values of the same type:
fun fiveValues() = arrayOf("Berlin", "Munich", "Amsterdam", "Madrid", "Vienna")
fun main() {
val (v1, v2, v3, v4, v5) = fiveValues()
}
The Kotlin limit is five because it has defined five componentN extension functions on arrays and collections.
4.4. data class
Sometimes, it’s better to define a custom type with a meaningful name. This way, in addition to using the destructuring syntactic sugar, we can convey a particular meaning in the function declaration:
data class Pod(val name: String, val ip: InetAddress, val assignedNode: String)
fun getUniquePod() = Pod("postgres", InetAddress.getLocalHost(), "Node 1")
fun main() {
val (podName, ip, assignedNode) = getUniquePod()
}
*Here, we’re taking advantage of the fact that data classes can be used in destructuring declarations to return multiple values*. In this particular example, the name of the data class clearly states that we’re dealing with a Kubernetes Pod — this is more readable compared to previous approaches.
4.5. Custom componentN Functions
Finally, sometimes we can even declare componentN functions as extension functions to take advantage of destructuring declarations:
operator fun KeyPair.component1(): ByteArray = public.encoded
operator fun KeyPair.component2(): ByteArray = private.encoded
fun getRsaKeyPair(): KeyPair = KeyPairGenerator.getInstance("RSA").genKeyPair()
fun main() {
val (publicKey, privateKey) = getRsaKeyPair()
}
In the above example, we’re defining two componentN functions on the java.security.KeyPair type to extract public and private keys in a more idiomatic way! This can be particularly useful when using already defined types and functions.
5. Conclusion
In this article, we talked about the fact that Kotlin doesn’t support tuples, so we can’t return multiple values from functions using tuples.
Despite this bad news, we can still use destructuring declarations to mimic the tuple behavior for returning multiple values from a function. More specifically, we can use built-in types such Pair and Triple, collection types and arrays, and finally, data classes, to return multiple values. Depending on the situation and the number of values and their types, we can use different approaches.
As usual, all the examples are available over on GitHub.