# Destructuring Declarations

Suppose you want to return more than one item from a function, such as a result along with some information about that result.

The Pair class, which is part of the standard library, allows you to return two values:

// Destructuring/Pairs.kt
package destructuring
import atomictest.eq

fun compute(input: Int): Pair<Int, String> =
  if (input > 5)
    Pair(input * 2, "High")
  else
    Pair(input * 2, "Low")

fun main() {
  compute(7) eq Pair(14, "High")
  compute(4) eq Pair(8, "Low")
  val result = compute(5)
  result.first eq 10
  result.second eq "Low"
}

We specify the return type of compute() as Pair<Int, String>. A Pair is a parameterized type, like List or Set.

Returning multiple values is helpful, but we’d also like a convenient way to unpack the results. As shown above, you can access the components of a Pair using its first and second properties, but you can also declare and initialize several identifiers simultaneously using a destructuring declaration:

val (a, b, c) = composedValue

This destructures a composed value and positionally assigns its components. The syntax differs from defining a single identifier—for destructuring, you put the names of the identifiers inside parentheses.

Here’s a destructuring declaration for the Pair returned from compute():

// Destructuring/PairDestructuring.kt
import destructuring.compute
import atomictest.eq

fun main() {
  val (value, description) = compute(7)
  value eq 14
  description eq "High"
}

The Triple class combines three values, but that’s as far as it goes. This is intentional: if you need to store more values, or if you find yourself using many Pairs or Triples, consider creating special classes instead.

[data Classes](javascript:void(0)) automatically allow destructuring declarations:

// Destructuring/Computation.kt
package destructuring
import atomictest.eq

data class Computation(
  val data: Int,
  val info: String
)

fun evaluate(input: Int) =
  if (input > 5)
    Computation(input * 2, "High")
  else
    Computation(input * 2, "Low")

fun main() {
  val (value, description) = evaluate(7)
  value eq 14
  description eq "High"
}

It’s clearer to return a Computation instead of a Pair<Int, String>. Choosing a good name for the result is almost as important as choosing a good self-explanatory name for the function itself. Adding or removing Computation information is simpler if it’s a separate class rather than a Pair.

When you unpack an instance of a data class, you must assign values to the new identifiers in the same order you define the properties in the class:

// Destructuring/Tuple.kt
package destructuring
import atomictest.eq

data class Tuple(
  val i: Int,
  val d: Double,
  val s: String,
  val b: Boolean,
  val l: List<Int>
)

fun main() {
  val tuple = Tuple(
    1, 3.14, "Mouse", false, listOf())
  val (i, d, s, b, l) = tuple
  i eq 1
  d eq 3.14
  s eq "Mouse"
  b eq false
  l eq listOf()

  val (_, _, animal) = tuple   // [1]
  animal eq "Mouse"
}
  • [1] If you don’t need some of the identifiers, you may use underscores instead of their names, or omit them completely if they appear at the end. Here, the unpacked values 1 and 3.14 are discarded using underscores, "Mouse" is captured into animal, and false and the empty List are discarded because they are at the end of the list.

The properties of a data class are assigned by order, not by name. If you destructure an object and later add a property anywhere except the end of its data class, that new property will be destructured on top of your previous identifier, producing unexpected results (see Exercise 3). If your custom data class has properties with identical types, the compiler can’t detect misuse so you may want to avoid destructuring it. Destructuring library data classes like Pair or Triple is safe, because they don’t change.

Using a for loop, you can iterate over a Map or a List of pairs (or other data classes) and destructure each element:

// Destructuring/ForLoop.kt
import atomictest.eq

fun main() {
  var result = ""
  val map = mapOf(1 to "one", 2 to "two")
  for ((key, value) in map) {
    result += "$key = $value, "
  }
  result eq "1 = one, 2 = two,"

  result = ""
  val listOfPairs =
    listOf(Pair(1, "one"), Pair(2, "two"))
  for ((i, s) in listOfPairs) {
    result += "($i, $s), "
  }
  result eq "(1, one), (2, two),"
}

withIndex() is a standard library extension function for List. It returns a collection of IndexedValues, which can be destructured:

// Destructuring/LoopWithIndex.kt
import atomictest.trace

fun main() {
  val list = listOf('a', 'b', 'c')
  for ((index, value) in list.withIndex()) {
    trace("$index:$value")
  }
  trace eq "0:a 1:b 2:c"
}

Destructuring declarations are only allowed for local vars and vals, and cannot be used to create class properties.

Exercises and solutions can be found at www.AtomicKotlin.com.