copy() and Destructuring

copy(), Destructuring and componentN()

copy() takes each constructor property as a named argument defaulting to its current value, so you name only what changes, like { ...book, price: 21.6 } in JavaScript. val (a, b) = x calls x.component1() and x.component2(), generated in constructor order (as in Loops and Ranges).

Copying, destructuring and the shallow-copy trap (copy.kt)Kotlin
data class Book(val id: Int, val title: String, val price: Double, val inStock: Boolean = true)
data class Shelf(val name: String, val books: MutableList<Book>)
fun main() {
  val book = Book(3, "Salt and Saffron", 24.00, inStock = false)
  val sale = book.copy(price = 21.60, inStock = true)
  println(sale)
  val (id, title, price) = sale                 // component1() .. component3()
  val (_, cost) = sale                          // position 2 is title, not price!
  println("#$id $title $price, cost = $cost")
  val mine = Shelf("To read", mutableListOf(book))
  val gifts = mine.copy(name = "Gift ideas")    // shallow: the list is shared
  gifts.books += sale
  println("${mine.books.size} ${mine.books === gifts.books}")
}
Output
Book(id=3, title=Salt and Saffron, price=21.6, inStock=true)
#3 Salt and Saffron 21.6, cost = Salt and Saffron
2 true

Two traps. Destructuring is positional, so cost silently got the title, and copy() is shallow: both shelves share one MutableList. Use a read-only List and replace it: copy(books = books + sale).

Kotlin is fixing the first trap. With the experimental -Xname-based-destructuring=name-mismatch, kotlinc 2.4.20 warns "variable name 'cost' differs from accessed property name 'title'", and it accepts (val cost = price) = sale, which binds by name (complete mode adds val [id, title] = sale for position). Without the option both forms are errors ("only available since language version 2.5").