2016 - Kotlin 1.0 May 21, 2024 - Kotlin 2.0 "A general purpose, statically typed, object-oriented alternative JVM programming language with type inference"
int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } @Override public boolean equals(Object o) { if (this = = o) return true; if (o == null | | getClass() ! = o.getClass()) return false; Person person = (Person) o; if (age != person.age) return false; return name != null ? name.equals(person.name) : person.name == null; } @Override public int hashCode() { int result = name != null ? name.hashCode() : 0; result = 31 * result + age; return result; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } } data class Person( val name: String, val age: Int ) It used to be very easy to create the wow effect with this:
= "Anton" lastName = "Arhipov" twitter { handle = "@antonarhipov" } company { name = "JetBrains" city = "Tallinn" } dob = 24 March 2000 } println("Created client is: ${client.toS}")
Coroutines - Inline / Value classes - Trailing comma - fun inter f aces - Type aliases - Sealed classes & inter f aces - Contracts - break/continue inside when - Exhaustive when statements - Builder inference - . . < operator - Data objects
Coroutines - Inline / Value classes - Trailing comma - fun inter f aces - Type aliases - Sealed classes & inter f aces - 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 - Definitely non-nullable types - Instantiation of annotation classes - Suppor t for JSpecify - suspend functions as super t ypes - Secondary constructors for inline value classes A LOT
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 per f ormance
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 per f ormance
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 per f ormance
} for (n in list) { println(n) } val <interator> = list.interator() while(<iterator>.hasNext()){ val s = <iterator>.next() println(s) } Frontend Intermediate Representation (FIR)
} 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)
KT-7186 Smar t cast for captured variables inside changing closures of inline functions - KT-4113 Smar t casts for proper t ies to not-null functional types at invoke calls - KT-25747 DFA variables: propagate smar t cast results from local variables - KT-1982 Smar t cast to a common super t ype of subject types after || (OR operator) - .
fun petAnimal(animal: Any) { val isCat = animal is Cat if (isCat) { animal.purr() } } Smar t -casts from variables Kotlin 2.0 : synthetic data flow variables propagate information about smar t -casts
applyDiscount(order) order is MonthlySubscription -> startSubscription(order) order is OneTimeOrder -> processOrder(order) } val order = getOrder() Potentially a logical error Repetition is not nice
applyDiscount(order) order is YearlySubscription -> processSubscription(order) order is MonthlySubscription -> startSubscription(order) order is OneTimeOrder -> processOrder(order) } val order = getOrder()
is YearlySubscription -> processSubscription(order) is MonthlySubscription -> startSubscription(order) is OneTimeOrder -> processOrder(order) } val order = getOrder()
is YearlySubscription -> processSubscription(order) is MonthlySubscription -> startSubscription(order) is OneTimeOrder -> processOrder(order) } val order = getOrder() Error: expecting ' -> ' &&
is YearlySubscription -> processSubscription(order) is MonthlySubscription -> startSubscription(order) is OneTimeOrder -> processOrder(order) } val order = getOrder() Guarded conditions: KEEP - 371 if
is YearlySubscription -> processSubscription(order) is MonthlySubscription -> startSubscription(order) is OneTimeOrder -> processOrder(order) } val order = getOrder()
> 100 -> applyDiscount(order) is MonthlySubscription -> startSubscription(order) is OneTimeOrder -> processOrder(order) } val order = getOrder() 'when' branch is never reachable
is YearlySubscription -> processSubscription(order) is MonthlySubscription -> startSubscription(order) is OneTimeOrder -> processOrder(order) } val order = getOrder() 2.1
is YearlySubscription -> processSubscription(order) is MonthlySubscription -> startSubscription(order) is OneTimeOrder -> processOrder(order) } val order = getOrder() 2.1 2.2 Stabe
is YearlySubscription -> processSubscription(order) is MonthlySubscription -> startSubscription(order) is OneTimeOrder -> processOrder(order) } val order = getOrder()
val (id, name, amount) = order println("Order $id: $name $amount") } is YearlySubscription -> processSubscription(order) ... val order = getOrder() Destructuring
val (id, name, amount) = order println("Order $id: $name $amount") } is YearlySubscription -> processSubscription(order) ... val order = YearlySubscription("1", "Anton", 12.0, 2024 OCTOBER 9) Destructuring Order 1: Anton, 12.0
val (name, id, amount) = order println("Order $id: $name $amount") } is YearlySubscription -> processSubscription(order) ... val order = YearlySubscription("1", "Anton", 12.0, 2024 OCTOBER 9) Variable name 'id' matches the name of a different component Destructuring
val (name, id, amount) = order println("Order $id: $name $amount") } is YearlySubscription -> processSubscription(order) ... val order = YearlySubscription("1", "Anton", 12.0, 2024 OCTOBER 9) Order Anton: 1, 12.0 Destructuring
val (name, id, amount) = order println("Order $id: $name $amount") } is YearlySubscription -> processSubscription(order) ... val order = YearlySubscription("1", "Anton", 12.0, 2024 OCTOBER 9) Error: 'name' doesn’t match the proper t y 'customerName' Name-based destructuring
val (name, id, amount) = order println("Order $id: $name $amount") } is YearlySubscription -> processSubscription(order) ... val order = YearlySubscription("1", "Anton", 12.0, 2024 OCTOBER 9) Name-based destructuring
val (name, id, amount) = order println("Order $id: $name $amount") } is YearlySubscription -> processSubscription(order) ... val order = YearlySubscription("1", "Anton", 12.0, 2024 OCTOBER 9) Name-based destructuring Existing syntax
(val name, val id, val amount) = order println("Order $id: $name $amount") } is YearlySubscription -> processSubscription(order) ... val order = YearlySubscription("1", "Anton", 12.0, 2024 OCTOBER 9) Name-based destructuring
(val name, val id, val amount) = order println("Order $id: $name $amount") } is YearlySubscription -> processSubscription(order) ... val order = YearlySubscription("1", "Anton", 12.0, 2024 OCTOBER 9) Name-based destructuring sealed interface Order data class YearlySubscription(val productName: String, ... ) : Order Does not rely on data classes
(val name, val id, val amount) = order println("Order $id: $name $amount") } is YearlySubscription -> processSubscription(order) ... val order = YearlySubscription("1", "Anton", 12.0, 2024 OCTOBER 9) Name-based destructuring sealed interface Order data class YearlySubscription(val productName: String, ... ) : Order Experimental in 2.4 Does not rely on data classes
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?
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
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?
Month.MARCH, this) DSL library buildClient { name = "Bob" birthday = 10 March 2000 } User code val dob = 10 March 2000 Context parameters (KEEP - 367 )
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 )
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 ) Beta in 2.2
do those examples have in common? Can we prevent this? Let's repor t every unused value! Not always true, unfor t unately: There are results which are supposed to be used and there are results which are auxiliary.