Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin 2.0 and Beyond

Anton Arhipov
September 04, 2024

Kotlin 2.0 and Beyond

Kotlin 2.0, released in May 2024, marked a significant milestone in the language's evolution. At the heart of this major version lies the new compiler front-end, codenamed K2. The release delivered better performance and stabilization of the language features across multiple compilation targets. Despite being a major release, Kotlin 2.0 prioritized a smooth migration path, focusing on refinement rather than introducing drastic changes.

We will first take a look at improvements in Kotlin 2.0 release, highlighting the introduction of frontend intermediate representation (FIR) and the new control flow engine.

Then, we'll shift our focus to the horizon, and discuss the new ideas on Kotlin's roadmap:
* Guard conditions - enhancing control flow and null safety
* Context parameters - improving code organization
* Union types for errors - bringing more expressiveness to type systems
* Named-based destructuring - for better readability and reducing errors
* Contracts - enabling more precise static analysis

Whether you're a seasoned Kotlin developer or just starting out, this talk promises to expand your understanding of the language.

Anton Arhipov

September 04, 2024
Tweet

More Decks by Anton Arhipov

Other Decks in Programming

Transcript

  1. July 20, 2011 - Kotlin announced at JVMLS February 15,

    2016 - Kotlin 1.0 May 21, 2024 - Kotlin 2.0
  2. July 20, 2011 - Kotlin announced at JVMLS February 15,

    2016 - Kotlin 1.0 May 21, 2024 - Kotlin 2.0
  3. July 20, 2011 - Kotlin announced at JVMLS February 15,

    2016 - Kotlin 1.0 2020 - Anton joined Kotlin team
  4. July 20, 2011 - Kotlin announced at JVMLS February 15,

    2016 - Kotlin 1.0 2020 ANTON KOTLIN FANBOYS Reality ANTON JAVA DEVELOPERS Expectation Me becoming Kotlin advocate:
  5. July 20, 2011 - Kotlin announced at JVMLS February 15,

    2016 - Kotlin 1.0 May 21, 2024 - Kotlin 2.0
  6. Features added after Kotlin 1.0: - Multiplatform projects - Coroutines

    - Inline / Value classes - Trailing comma - fun interfaces - ...
  7. Features added after Kotlin 1.0: - Multiplatform projects - Coroutines

    - Inline / Value classes - Trailing comma - fun interfaces - Type aliases - Sealed classes & interfaces - Contracts - break/continue inside when - Exhaustive when statements - Builder inference - . . < operator - Data objects
  8. Features added after Kotlin 1.0: - Multiplatform projects - Coroutines

    - Inline / Value classes - Trailing comma - fun interfaces - Type aliases - Sealed classes & interfaces - Contracts - break/continue inside when - Exhaustive when statements - Builder inference - ..< operator - Data objects - provideDelegate - Bound callable references - Destructuring in lambdas - Array literals in annotations - Local lateinit variables - Opt-in annotations - De fi nitely non-nullable types - Instantiation of annotation classes - Support for JSpecify - suspend functions as supertypes - Secondary constructors for inline value classes
  9. class MyFun<T>(var param: T): () - > Result<T> { override

    fun invoke(): Result<T> { / /... } } fun <T> handle(handler: () -> Result<T>) { //. .. } ‘C’ for consistency!
  10. class MyFun<T>(var param: T): () - > Result<T> { override

    fun invoke(): Result<T> { / /... } } fun <T> handle(handler: () -> Result<T>) { //. .. } ‘C’ for consistency! Functional type
  11. class MyFun<T>(var param: T): suspend () - > Result<T> {

    override suspend fun invoke(): Result<T> { / /... } } fun <T> handle(handler: suspend () -> Result<T>) { //. .. } ‘C’ for consistency! Forbidden before 1.6
  12. class MyFun<T>(var param: T): suspend () - > Result<T> {

    override suspend fun invoke(): Result<T> { / /... } } fun <T> handle(handler: suspend () -> Result<T>) { //. .. } ‘C’ for consistency! Fixed in 1.6
  13. fun process(supplier: Supplier<String>) = supplier.get() fun main() { process {

    "hello!" } } SAM type SAM conversion ‘C’ for consistency!
  14. fun process(supplier: Supplier<String> = Supplier { "hello!" }) = supplier.get()

    fun main() { val supplier: Supplier<String> = Supplier { "hello!" } process(supplier) } ‘C’ for consistency!
  15. fun process(supplier: Supplier<String> = Supplier { "hello!" }) = supplier.get()

    fun main() { val supplier: Supplier<String> = Supplier { "hello!" } process(supplier) } Type name required ‘C’ for consistency!
  16. K2: The new Kotlin compiler - why? 1. A few

    language features have appeared unexpectedly in Kotlin Hard to maintain and evolve the compiler 2. Interaction with compiler and IDEs Many ad-hoc solutions, no strict contracts, and no stable API 3. Compilation time performance
  17. K2: The new Kotlin compiler - why? 1. A few

    language features have appeared unexpectedly in Kotlin Hard to maintain and evolve the compiler 2. Interaction with compiler and IDEs Many ad-hoc solutions, no strict contracts, and no stable API 3. Compilation time performance
  18. K2: The new Kotlin compiler - why? 1. A few

    language features have appeared unexpectedly in Kotlin Hard to maintain and evolve the compiler 2. Interaction with compiler and IDEs Many ad-hoc solutions, no strict contracts, and no stable API 3. Compilation time performance
  19. Kotlin 2.0 More than 80 features in the di ff

    erent subsystems Around 25 and small improvements within the language Main focus is on correctness and performance
  20. if (condition) { println("Hello") } for (n in list) {

    println(n) } val (a, b) = "a" to "b" Frontend Intermediate Representation (FIR)
  21. if (condition) { println("Hello") } when { condition -> println("Hello")

    } for (n in list) { println(n) } val <interator> = list.interator() while(<iterator>.hasNext()){ val s = <iterator>.next() println(s) } val (a, b) = "a" to "b" val <pair> = "a" to "b" val a = pair.component1() val b = pair.component2() Frontend Intermediate Representation (FIR)
  22. fun mutate(ml: MutableList<Long>) { ml[0] = ml[0] + 1 }

    Combination of Long and Integer Literal Types Frontend Intermediate Representation (FIR)
  23. fun mutate(ml: MutableList<Long>) { ml[0] = ml[0] + 1 }

    Combination of Long and Integer Literal Types Long Integer Literal Type Frontend Intermediate Representation (FIR)
  24. fun mutate(ml: MutableList<Long>) { ml[0] += 1 } Combination of

    Long and Integer Literal Types Error: 1L is required // Error in Kotlin 1.x Frontend Intermediate Representation (FIR)
  25. fun mutate(ml: MutableList<Long>) { ml[0] += 1 } Combination of

    Long and Integer Literal Types // OK in 2.0 Desugared into: ml.set(0, ml.get(0).plus(1)) Frontend Intermediate Representation (FIR)
  26. Combination of nullable operator-calls class Box(val ml: MutableList<Long>) fun mutate(box:

    Box?) { box ?. ml[0] += 1 // Error in 1.x box ?. ml[0] += 1L // Error in 1.x } Frontend Intermediate Representation (FIR)
  27. Combination of nullable operator-calls class Box(val ml: MutableList<Long>) fun mutate(box:

    Box?) { box ?. ml[0] += 1 // OK in 2.0 } box ?. run { ml.set(0, ml.get(0).plus(1))} Desugared into: Frontend Intermediate Representation (FIR)
  28. New control fl ow engine read: more smart-casts! - KT-7186

    Smart cast for captured variables inside changing closures of inline functions - KT-4113 Smart casts for properties to not-null functional types at invoke calls - KT-25747 DFA variables: propagate smart cast results from local variables - KT-1982 Smart cast to a common supertype of subject types after || (OR operator) - ...
  29. class Cat { fun purr() { println("Purr purr") } }

    fun petAnimal(animal: Any) { if (animal is Cat) { animal.purr() } } Smart-casts
  30. class Cat { fun purr() { println("Purr purr") } }

    fun petAnimal(animal: Any) { if (animal is Cat) { animal.purr() } } Smart-casts
  31. class Cat { fun purr() { println("Purr purr") } }

    fun petAnimal(animal: Any) { val isCat = animal is Cat if (isCat) { animal.purr() // Error in Kotlin 1.x } } Smart-casts from variables
  32. class Cat { fun purr() { println("Purr purr") } }

    fun petAnimal(animal: Any) { val isCat = animal is Cat if (isCat) { animal.purr() // Error in Kotlin 1.x } } Smart-casts from variables Kotlin 1.x: variables don't carry any data fl ow information
  33. class Cat { fun purr() { println("Purr purr") } }

    fun petAnimal(animal: Any) { val isCat = animal is Cat if (isCat) { animal.purr() // OK in Kotlin 2.0 } } Smart-casts from variables Kotlin 2.0: synthetic data fl ow variables propagate information about smart-casts
  34. Smart-casts from variables class Card(val holder: String?) fun findHolder(card: Any):

    String { val cardWithHolder = card is Card && !card.holder.isNullOrEmpty() return when { cardWithHolder -> { card.holder } else -> "none" } }
  35. Smart-casts from variables class Card(val holder: String?) fun findHolder(card: Any):

    String { val cardWithHolder = card is Card && !card.holder.isNullOrEmpty() return when { cardWithHolder -> { card.holder } else -> "none" } } Any -> Card String? -> String Smart-cast to String
  36. fun indexOfMax(a: IntArray): Int? { var maxI: Int? = null

    a.forEachIndexed { i, value -> if (maxI == null || a[maxI !! ] <= value) { maxI = i } } return maxI } Smart-casts inside closures of inline lambdas
  37. fun indexOfMax(a: IntArray): Int? { var maxI: Int? = null

    a.forEachIndexed { i, value -> if (maxI == null || a[maxI !! ] <= value) { maxI = i } } return maxI } Smart-casts inside closures of inline lambdas Kotlin 1.x: Smart cast to 'Int' is impossible. 'maxI' is a local variable that is captured by a changing closure
  38. fun indexOfMax(a: IntArray): Int? { var maxI: Int? = null

    a.forEachIndexed { i, value -> if (maxI == null || a[maxI] <= value) { maxI = i } } return maxI } Smart-casts inside closures of inline lambdas Kotlin 2.0: treats inline functions as having an implicit callInPlace contract
  39. Smart-casts after || interface Status { fun signal() } interface

    Ok : Status interface Postponed : Status interface Declined : Status
  40. interface Status { fun signal() } interface Ok : Status

    interface Postponed : Status interface Declined : Status Smart-casts after ||
  41. Smart-casts after || fun alert(signalStatus: Any) { if (signalStatus is

    Postponed || signalStatus is Declined) { signalStatus.signal() // Error 1.x: the inferred type is Any } }
  42. Smart-casts after || fun alert(signalStatus: Any) { if (signalStatus is

    Postponed || signalStatus is Declined) { signalStatus.signal() // OK in 2.0 } }
  43. Smart-casts after || fun alert(signalStatus: Any) { if (signalStatus is

    Postponed || signalStatus is Declined) { signalStatus.signal() // OK in 2.0 } } Kotlin 2.0: merge to a common supertype (e.g. Status)
  44. What's next for Kotlin? Guards: pattern matching without binding Context-sensitive

    resolution Data classes improvements E ff ect system capabilities Data recognition and decunstruction
  45. What's next for Kotlin? Guards: pattern matching without binding Context-sensitive

    resolution Data classes improvements E ff ect system capabilities Data recognition and decunstruction Language features for libraries Union types for errors "dataarg" classes Context parameters
  46. @Composable fun DisplayLastSearchResultByPanelType( searchData: Sequence<Data>, id: String ) { LazyColumn

    { val lastData = searchData.last { it.id == id } val searchPanel = selectedSearchPanel() when { searchPanel is SearchPanel.NewsPanel && !searchPanel.isBlocked -> item { NewResult(lastData) } searchPanel == SearchPanel.SpeakersPanel - > item { } searchPanel == SearchPanel.TalksPanel -> item { } } } }
  47. @Composable fun DisplayLastSearchResultByPanelType( searchData: Sequence<Data>, id: String ) { LazyColumn

    { val lastData = searchData.last { it.id == id } val searchPanel = selectedSearchPanel() when { searchPanel is SearchPanel.NewsPanel && !searchPanel.isBlocked -> item { NewResult(lastData) } searchPanel == SearchPanel.SpeakersPanel - > item { } searchPanel == SearchPanel.TalksPanel -> item { } } } }
  48. @Composable fun DisplayLastSearchResultByPanelType( searchData: Sequence<Data>, id: String ) { LazyColumn

    { val lastData = searchData.last { it.id == id } val searchPanel = selectedSearchPanel() when { searchPanel is SearchPanel.NewsPanel && !searchPanel.isBlocked -> item { NewResult(lastData) } searchPanel == SearchPanel.SpeakersPanel - > item { } searchPanel == SearchPanel.TalksPanel -> item { } } } }
  49. @Composable fun DisplayLastSearchResultByPanelType( searchData: Sequence<Data>, id: String ) { LazyColumn

    { val lastData = searchData.last { it.id == id } when(val searchPanel = selectedSearchPanel()) { is SearchPanel.NewsPanel & & !searchPanel.isBlocked - > item { NewResult(lastData) } is SearchPanel.SpeakersPanel -> item { } is SearchPanel.TalksPanel -> item { } } } }
  50. @Composable fun DisplayLastSearchResultByPanelType( searchData: Sequence<Data>, id: String ) { LazyColumn

    { val lastData = searchData.last { it.id == id } when(val searchPanel = selectedSearchPanel()) { is SearchPanel.NewsPanel & & !searchPanel.isBlocked - > item { NewResult(lastData) } is SearchPanel.SpeakersPanel -> item { } is SearchPanel.TalksPanel -> item { } } } } Error: expecting ' -> ' &&
  51. @Composable fun DisplayLastSearchResultByPanelType( searchData: Sequence<Data>, id: String ) { LazyColumn

    { val lastData = searchData.last { it.id == id } when(val searchPanel = selectedSearchPanel()) { is SearchPanel.NewsPanel if !searchPanel.isBlocked - > item { NewResult(lastData) } is SearchPanel.SpeakersPanel -> item { } is SearchPanel.TalksPanel -> item { } } } } Guarded conditions: KEEP-371 Coming as Beta in 2.1 if
  52. @Composable fun DisplayLastSearchResultByPanelType( searchData: Sequence<Data>, id: String ) { LazyColumn

    { val lastData = searchData.last { it.id == id } when(val searchPanel = selectedSearchPanel()) { is SearchPanel.NewsPanel if !searchPanel.isBlocked - > item { NewResult(lastData) } is SearchPanel.SpeakersPanel -> item { } is SearchPanel.TalksPanel -> item { } } } } Can we do better?
  53. @Composable fun DisplayLastSearchResultByPanelType( searchData: Sequence<Data>, id: String ) { LazyColumn

    { val lastData = searchData.last { it.id == id } when(val searchPanel = selectedSearchPanel()) { is SearchPanel.NewsPanel if !searchPanel.isBlocked - > item { NewResult(lastData) } is SearchPanel.SpeakersPanel -> item { } is SearchPanel.TalksPanel -> item { } } } } Can we do better?
  54. @Composable fun DisplayLastSearchResultByPanelType( searchData: Sequence<Data>, id: String ) { LazyColumn

    { val lastData = searchData.last { it.id == id } when(val searchPanel = selectedSearchPanel()) { is NewsPanel if !searchPanel.isBlocked - > item { NewResult(lastData) } is SpeakersPanel -> item { } is TalksPanel -> item { } } } } Coming as experimental in 2.2 Context-sensitive resolution KEEP-379 No extra quali fi er
  55. Context-sensitive resolution enum class Status { Ok, Fail } fun

    process(status: Status = Status.Ok) { ... }
  56. Context-sensitive resolution enum class Status { Ok, Fail } fun

    process(status: Status = Status.Ok) { ... }
  57. val user = User("Anton", "Arhipov") val (firstName, surname) = user

    println(surname) // Arhipov println(firstName) // Anton Descructuring data class User(val name: String, val lastName: String)
  58. val user = User("Anton", "Arhipov") val (surname, firstName) = user

    println(surname) // Anton println(firstName) // Arhipov Descructuring data class User(val name: String, val lastName: String)
  59. val user = User("Anton", "Arhipov") val (surname, firstName) = user

    Name-based descructuring Error in 2.x: -“surname” doesn’t match the property “name” -“ fi rstName” doesn’t match the property “lastName” data class User(val name: String, val lastName: String)
  60. LazyColumn { val lastData = searchData.last { it.id == id

    } val searchPanel = selectedSearchPanel() when { searchPanel is SearchPanel.NewsPanel && !searchPanel.isBlocked -> item { NewResult(lastData) } searchPanel == SearchPanel.SpeakersPanel - > item { } searchPanel == SearchPanel.TalksPanel -> item { } } }
  61. @Composable fun LazyColumn( modifier: Modifier = Modifier, state: LazyListState =

    rememberLazyListState(), contentPadding: PaddingValues = PaddingValues(0.dp), reverseLayout: Boolean = false, verticalArrangement: Arrangement.Vertical = if (!reverseLayout) Arrangement.Top else Arrangement.Bottom, horizontalAlignment: Alignment.Horizontal = Alignment.Start, flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(), userScrollEnabled: Boolean = true, content: LazyListScope.() -> Unit ) { /* ... */ }
  62. API evolution of optional parameters Adding new parameter with a

    default value can be backwards incompatible, leading to new overlads of the function
  63. @Composable fun LazyColumn( modifier: Modifier = Modifier, state: LazyListState =

    rememberLazyListState(), contentPadding: PaddingValues = PaddingValues(0.dp), reverseLayout: Boolean = false, verticalArrangement: Arrangement.Vertical = if (!reverseLayout) Arrangement.Top else Arrangement.Bottom, horizontalAlignment: Alignment.Horizontal = Alignment.Start, flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(), userScrollEnabled: Boolean = true, content: LazyListScope.() -> Unit ) { /* ... */ }
  64. dataarg class ColumnSettings( val contentPadding: PaddingValues = PaddingValues(0.dp), val reverseLayout:

    Boolean = false, val verticalArrangement: Arrangement.Vertical = if (!reverseLayout) Arrangement.Top else Arrangement.Bottom, val horizontalAlignment: Alignment.Horizontal = Alignment.Start, val userScrollEnabled: Boolean = true ) Extensible data arguments
  65. dataarg class ColumnSettings( val contentPadding: PaddingValues = PaddingValues(0.dp), val reverseLayout:

    Boolean = false, val verticalArrangement: Arrangement.Vertical = if (!reverseLayout) Arrangement.Top else Arrangement.Bottom, val horizontalAlignment: Alignment.Horizontal = Alignment.Start, val userScrollEnabled: Boolean = true ) @Composable fun LazyColumn( modifier: Modifier = Modifier, state: LazyListState = rememberLazyListState(), dataarg args: ColumnSettings, flingBehavior: FlingBehavior = .. . , content: @Composable RowScope.() -> Unit ) { /* .. . * / } LazyColumn(reverseLayout = true) { //... }
  66. LazyColumn { val lastData = searchData.last { it.id == id

    } val searchPanel = selectedSearchPanel() when { searchPanel is SearchPanel.NewsPanel && !searchPanel.isBlocked -> item { NewResult(lastData) } searchPanel == SearchPanel.SpeakersPanel - > item { } searchPanel == SearchPanel.TalksPanel -> item { } } }
  67. /** * Returns the last element matching the given [predicate].

    */ public inline fun <T> Sequence<T>.last(predicate: (T) -> Boolean): T { var result: T? = null for (element in this) if (predicate(element)) result = element return result ? : throw NoSuchElementException("Not found") } Find last matching element in the sequence
  68. What if the predicate is '{ it == null }'

    /** * Returns the last element matching the given [predicate]. */ public inline fun <T> Sequence<T>.last(predicate: (T) -> Boolean): T { var last: T? = null var found = false for (element in this) { if (predicate(element)) { last = element found = true } } if (!found) throw NoSuchElementException("Not found") @Suppress("UNCHECKED_CAST") return last as T }
  69. private object NotFound fun <T> Sequence<T>.last(predicate: (T) -> Boolean): T

    { var result: Any? = NotFound for (element in this) if (predicate(element)) result = element if (result == = NotFound) throw NoSuchElementException("Not found") return result as T } Can we do better?
  70. private object NotFound fun <T> Sequence<T>.last(predicate: (T) -> Boolean): T

    { var result: Any? = NotFound for (element in this) if (predicate(element)) result = element if (result == = NotFound) throw NoSuchElementException("Not found") return result as T } Use of 'Any?' type Unchecked cast Can we do better?
  71. Union types for errors private error object NotFound fun <T>

    Sequence<T>.last(predicate: (T) -> Boolean): T { var result: T | NotFound = NotFound for (element in this) if (predicate(element)) result = element if (result is NotFound) throw NoSuchElementException("Not found") return result } Union types for errors Automatic smart-cast In research
  72. https://vimeo.com/747697423 val client = buildClient { firstName = "Anton" lastName

    = "Arhipov" twitter { handle = "@antonarhipov" } company { name = "JetBrains" city = "Tallinn" } } println("Created client is: $client")
  73. class Client(var name: String? = null, var birthday: LocalDate? =

    null) fun buildClient(init: Client.() - > Unit): Client { var client = Client() client.init() return client } Use case: type-safe builders (a.k.a DSLs)
  74. class Client(var name: String? = null, var birthday: LocalDate? =

    null) fun buildClient(init: Client.() - > Unit): Client { var client = Client() client.init() return client } Use case: type-safe builders DSL library
  75. class Client(var name: String? = null, var birthday: LocalDate? =

    null) fun buildClient(init: Client.() - > Unit): Client { var client = Client() client.init() return client } Use case: type-safe builders DSL library buildClient { name = "Bob" birthday = LocalDate.of(2000, 3, 10) } User code
  76. class Client(var name: String? = null, var birthday: LocalDate? =

    null) fun buildClient(init: Client.() - > Unit): Client { var client = Client() client.init() return client } Use case: type-safe builders DSL library buildClient { name = "Bob" birthday = LocalDate.of(2000, 3, 10) } User code Can we do better?
  77. class Client(var name: String? = null, var birthday: LocalDate? =

    null) fun buildClient(init: Client.() - > Unit): Client { var client = Client() client.init() return client } Use case: type-safe builders DSL library buildClient { name = "Bob" birthday = 10 March 2000 } User code infix fun Int.March(year: Int) = LocalDate.of(year, Month.MARCH, this) val dob = 10 March 2000
  78. class Client(var name: String? = null, var birthday: LocalDate? =

    null) fun buildClient(init: Client.() - > Unit): Client { var client = Client() client.init() return client } Use case: type-safe builders DSL library buildClient { name = "Bob" birthday = 10 March 2000 } User code infix fun Int.March(year: Int) = LocalDate.of(year, Month.MARCH, this) val dob = 10 March 2000 How can we restrict the scope?
  79. object ClientBuilderContext context(_: ClientBuilderContext) infix fun Int.March(year: Int) = LocalDate.of(year,

    Month.MARCH, this) DSL library buildClient { name = "Bob" birthday = 10 March 2000 } User code val dob = 10 March 2000 Context parameters (KEEP-367)
  80. DSL library buildClient { name = "Bob" birthday = 10

    March 2000 } User code val dob = 10 March 2000 fun buildClient(init: context(ClientBuilderContext) Client.() - > Unit): Client = with(ClientBuilderContext()) { //. .. } object ClientBuilderContext context(_: ClientBuilderContext) infix fun Int.March(year: Int) = LocalDate.of(year, Month.MARCH, this) Context parameters (KEEP-367)
  81. DSL library buildClient { name = "Bob" birthday = 10

    March 2000 } User code val dob = 10 March 2000 fun buildClient(init: context(ClientBuilderContext) Client.() - > Unit): Client = with(ClientBuilderContext()) { //. .. } object ClientBuilderContext context(_: ClientBuilderContext) infix fun Int.March(year: Int) = LocalDate.of(year, Month.MARCH, this) Context parameters (KEEP-367)
  82. DSL library buildClient { name = "Bob" birthday = 10

    March 2000 } User code val dob = 10 March 2000 fun buildClient(init: context(ClientBuilderContext) Client.() - > Unit): Client = with(ClientBuilderContext()) { //. .. } object ClientBuilderContext context(_: ClientBuilderContext) infix fun Int.March(year: Int) = LocalDate.of(year, Month.MARCH, this) The required context is missing Required context available in this block Context parameters (KEEP-367)
  83. buildClient { name = "Bob" // 'name' property stays uninitialized

    birthday = 10 March 2000 } Or... What if the user forgets to assign a property?
  84. E ff ect system capabilities buildClient { name = "Bob"

    birthday = 10 March 2000 } In research
  85. E ff ect system capabilities buildClient { name = "Bob"

    birthday = 10 March 2000 } fun buildClient(init: context(ClientBuilderContext) Client.() -> Unit): Client { contract { called(init@name, ONCE) called(init@birthday, ONCE) } with(ClientBuilderContext()) { // ... } } In research
  86. E ff ect system capabilities buildClient { name = "Bob"

    birthday = 10 March 2000 } fun buildClient(init: context(ClientBuilderContext) Client.() -> Unit): Client { contract { called(init@name, ONCE) called(init@birthday, ONCE) } with(ClientBuilderContext()) { // ... } } Contract: "Ensure that the 'name' property is assigned once in the 'init' block" In research
  87. Summary Kotlin 2.0: new compiler & more smart-casts More features

    are coming for working with data Stronger abstractions and improvements in the type system