What's new in Kotlin 2.2.20
What's new in Kotlin 2.2.20
이 문서는 Kotlin 공식 문서를 한국어로 번역한 것입니다.
본문
Kotlin 2.2.20 릴리스가 나왔어요. 웹 개발에 중요한 변화를 가져오는 릴리스예요. Kotlin/Wasm이 Beta가 됐고, JavaScript interop의 예외 처리, npm 의존성 분리, 번들된 브라우저 디버깅 지원, 그리고 js와 wasmJs 타깃을 위한 새 공유 소스 세트가 개선됐어요.
추가로 주요 내용을 몇 가지 더 볼게요.
- Kotlin Multiplatform: Swift export가 기본으로 제공되고, Kotlin 라이브러리의 크로스 플랫폼 컴파일이 Stable됐으며, 공통 의존성을 선언하는 새 방식이 도입됐어요.
- 언어: suspend 함수 타입의 오버로드에 람다를 넘길 때의 오버로드 해석 개선.
- Kotlin/Native: Xcode 26, 스택 캐너리 지원, 릴리스 바이너리 크기 축소.
- Kotlin/JS:
Long값을 JavaScriptBigInt로 컴파일.
웹용 Compose Multiplatform이 Beta가 됐어요. 자세한 내용은 블로그 포스트에서 확인하세요.
이 업데이트들의 간략한 개요는 다음 영상에서도 볼 수 있어요.
Kotlin 릴리스 주기에 대한 내용은 Kotlin 릴리스 프로세스 문서를 참고해 주세요.
IDE 지원
Kotlin 2.2.20을 지원하는 Kotlin 플러그인은 최신 버전의 IntelliJ IDEA와 Android Studio에 번들로 포함돼 있어요. 업데이트하려면 빌드 스크립트에서 Kotlin 버전을 2.2.20으로 바꾸기만 하면 돼요.
자세한 내용은 새 릴리스로 업데이트하기를 참고해 주세요.
언어
Kotlin 2.2.20에서 Kotlin 2.3.0에 계획된 곧 출시될 언어 기능을 써 볼 수 있어요. 여기에는 suspend 함수 타입의 오버로드에 람다를 넘길 때의 오버로드 해석 개선과 명시적 반환 타입이 있는 식 본문에서 return 문 지원이 포함돼요. 또한 이번 릴리스는 when 식의 완전성(exhaustiveness) 검사, reified Throwable catch, Kotlin contracts도 개선했어요.
suspend 함수 타입의 오버로드에 람다를 넘길 때의 오버로드 해석 개선
이전에는 함수를 일반 함수 타입과 suspend 함수 타입 모두로 오버로드했을 때, 람다를 넘기면 모호성(ambiguity) 오류가 발생했어요. 명시적 타입 캐스트로 이 오류를 우회할 수 있었지만, 컴파일러가 잘못된 No cast needed 경고를 보고했어요.
// Defines two overloads
fun transform(block: () -> Int) {}
fun transform(block: suspend () -> Int) {}
fun test() {
// Fails with overload resolution ambiguity
transform({ 42 })
// Uses an explicit cast, but the compiler incorrectly reports
// a "No cast needed" warning
transform({ 42 } as () -> Int)
}
이 변경으로, 일반 함수 타입과 suspend 함수 타입 오버로드를 모두 정의하면 캐스트 없는 람다는 일반 오버로드로 해석돼요. suspend 오버로드로 명시적으로 해석하려면 suspend 키워드를 사용하세요.
// Resolves to transform(() -> Int)
transform({ 42 })
// Resolves to transform(suspend () -> Int)
transform(suspend { 42 })
이 동작은 Kotlin 2.3.0에서 기본으로 활성화될 예정이에요. 지금 테스트해 보려면 다음 컴파일러 옵션으로 언어 버전을 2.3으로 설정하세요.
-language-version 2.3
또는 build.gradle(.kts) 파일에서 구성할 수 있어요.
kotlin {
compilerOptions {
languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_3)
}
}
이슈 트래커 YouTrack에 피드백을 남겨 주시면 감사하겠어요.
명시적 반환 타입이 있는 식 본문에서 return 문 지원
이전에는 식 본문(expression body)에서 return을 사용하면 함수의 반환 타입이 Nothing으로 추론될 수 있어 컴파일러 오류가 발생했어요.
fun example() = return 42
// Error: Returns are prohibited for functions with an expression body
이 변경으로 이제 반환 타입을 명시적으로 작성하는 한 식 본문에서 return을 사용할 수 있어요.
// Specifies the return type explicitly
fun getDisplayNameOrDefault(userId: String?): String = getDisplayName(userId ?: return "default")
// Fails because it doesn't specify the return type explicitly
fun getDisplayNameOrDefault(userId: String?) = getDisplayName(userId ?: return "default")
마찬가지로, 식 본문이 있는 함수에서 람다와 중첩 식 안의 return 문은 이전에 의도치 않게 컴파일되곤 했어요. 이제 Kotlin은 반환 타입이 명시적으로 지정되는 한 이런 경우를 지원해요. 명시적 반환 타입이 없는 경우는 Kotlin 2.3.0에서 deprecated될 예정이에요.
// Return type isn't explicitly specified, and the return statement is inside a lambda
// which will be deprecated
fun returnInsideLambda() = run { return 42 }
// Return type isn't explicitly specified, and the return statement is inside the initializer
// of a local variable, which will be deprecated
fun returnInsideIf() = when {
else -> {
val result = if (someCondition()) return "" else "value"
result
}
}
이 동작은 Kotlin 2.3.0에서 기본으로 활성화될 예정이에요. 지금 테스트해 보려면 다음 컴파일러 옵션으로 언어 버전을 2.3으로 설정하세요.
-language-version 2.3
또는 build.gradle(.kts) 파일에서 구성할 수 있어요.
kotlin {
compilerOptions {
languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_3)
}
}
이슈 트래커 YouTrack에 피드백을 남겨 주시면 감사하겠어요.
when 식의 데이터 흐름 기반 완전성 검사
Kotlin 2.2.20은 when 식에 대한 데이터 흐름 기반 완전성(exhaustiveness) 검사를 도입했어요. 이전에는 컴파일러 검사가 when 식 자체로 제한되어, 불필요한 else 분기를 추가해야 하는 경우가 많았어요. 이 업데이트로 컴파일러가 이전 조건 검사와 조기 반환(early return)을 추적해서, 중복 else 분기를 제거할 수 있게 됐어요.
예를 들어 컴파일러는 if 조건을 만족하면 함수가 반환된다는 것을 이제 인식해서, when 식이 나머지 경우만 처리하면 되요.
enum class UserRole { ADMIN, MEMBER, GUEST }
fun getPermissionLevel(role: UserRole): Int {
// Covers the Admin case outside of the when expression
if (role == UserRole.ADMIN) return 99
return when (role) {
UserRole.MEMBER -> 10
UserRole.GUEST -> 1
// You no longer have to include this else branch
// else -> throw IllegalStateException()
}
}
이 기능은 Experimental이에요. 활성화하려면 build.gradle(.kts) 파일에 다음 컴파일러 옵션을 추가하세요.
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xdata-flow-based-exhaustiveness")
}
}
catch 절에서 reified 타입 지원
Kotlin 2.2.20에서 컴파일러는 이제 inline 함수의 catch 절에서 reified 제네릭 타입 파라미터를 쓸 수 있게 해 줘요.
예시를 볼게요.
inline fun <reified ExceptionType : Throwable> handleException(block: () -> Unit) {
try {
block()
// This is now allowed after the change
} catch (e: ExceptionType) {
println("Caught specific exception: ${e::class.simpleName}")
}
}
fun main() {
// Tries to perform an action that might throw an IOException
handleException<java.io.IOException> {
throw java.io.IOException("File not found")
}
// Caught specific exception: IOException
}
이전에는 inline 함수에서 reified Throwable 타입을 catch하려고 하면 오류가 발생했어요.
이 동작은 Kotlin 2.4.0에서 기본으로 활성화될 예정이에요. 지금 사용하려면 build.gradle(.kts) 파일에 다음 컴파일러 옵션을 추가하세요.
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xallow-reified-type-in-catch")
}
}
Kotlin 팀은 외부 기여자 Iven Krall의 기여에 감사하고 있어요.
개선된 Kotlin contracts
Kotlin 2.2.20은 Kotlin contracts에 여러 개선을 도입했어요.
- contract 타입 단언에서 제네릭 지원.
- 프로퍼티 접근자와 특정 연산자 함수 내부의 contract 지원.
- 조건이 충족될 때 null이 아닌 반환 값을 보장하는 contract의
returnsNotNull()함수 지원. - 람다 안으로 전달될 때 조건이 참이라고 가정할 수 있게 해 주는 새
holdsIn키워드.
이 개선들은 Experimental이에요. 옵트인하려면 여전히 contract를 선언할 때 @OptIn(ExperimentalContracts::class) 어노테이션을 사용해야 해요. holdsIn 키워드와 returnsNotNull() 함수는 @OptIn(ExperimentalExtendedContracts::class) 어노테이션도 필요해요.
이 개선들을 사용하려면 아래 각 섹션에서 설명하는 컴파일러 옵션도 추가해야 해요.
이슈 트래커에 피드백을 남겨 주시면 감사하겠어요.
contract 타입 단언에서 제네릭 지원
이제 제네릭 타입에 타입 단언을 수행하는 contract를 작성할 수 있어요.
import kotlin.contracts.*
sealed class Failure {
class HttpError(val code: Int) : Failure()
// Insert other failure types here
}
sealed class Result<out T, out F : Failure> {
class Success<T>(val data: T) : Result<T, Nothing>()
class Failed<F : Failure>(val failure: F) : Result<Nothing, F>()
}
@OptIn(ExperimentalContracts::class)
// Uses a contract to assert a generic type
fun <T, F : Failure> Result<T, F>.isHttpError(): Boolean {
contract {
returns(true) implies (this@isHttpError is Result.Failed<Failure.HttpError>)
}
return this is Result.Failed && this.failure is Failure.HttpError
}
이 예시에서 contract는 Result 객체에 타입 단언을 수행해서, 컴파일러가 이를 단언된 제네릭 타입으로 안전하게 스마트 캐스트할 수 있게 해 줘요.
이 기능은 Experimental이에요. 옵트인하려면 build.gradle(.kts) 파일에 다음 컴파일러 옵션을 추가하세요.
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xallow-contracts-on-more-functions")
}
}
프로퍼티 접근자와 특정 연산자 함수 내부의 contract 지원
이제 프로퍼티 접근자와 특정 연산자 함수 안에서 contract를 정의할 수 있어요. 이로써 더 많은 종류의 선언에서 contract를 사용할 수 있어 더 유연해져요.
예를 들어 getter 안에서 contract를 사용해 리시버 객체에 스마트 캐스트를 활성화할 수 있어요.
import kotlin.contracts.*
val Any.isHelloString: Boolean
get() {
@OptIn(ExperimentalContracts::class)
// Enables smart casting the receiver to String when the getter returns true
contract { returns(true) implies (this@isHelloString is String) }
return "hello" == this
}
fun printIfHelloString(x: Any) {
if (x.isHelloString) {
// Prints the length after the smart cast of the receiver to String
println(x.length)
// 5
}
}
추가로 다음 연산자 함수에서 contract를 사용할 수 있어요.
invokecontainsrangeTo,rangeUntilcomponentNiteratorunaryPlus,unaryMinus,notinc,dec
연산자 함수에서 contract를 사용해 람다 안의 변수 초기화를 보장하는 예시를 볼게요.
import kotlin.contracts.*
class Runner {
@OptIn(ExperimentalContracts::class)
// Enables initialization of variables assigned inside the lambda
operator fun invoke(block: () -> Unit) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
}
}
fun testOperator(runner: Runner) {
val number: Int
runner {
number = 1
}
// Prints the value after definite initialization guaranteed by the contract
println(number)
// 1
}
이 기능은 Experimental이에요. 옵트인하려면 build.gradle(.kts) 파일에 다음 컴파일러 옵션을 추가하세요.
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xallow-contracts-on-more-functions")
}
}
contract의 returnsNotNull() 함수 지원
Kotlin 2.2.20은 contract용 returnsNotNull() 함수를 도입했어요. 이 함수로 특정 조건이 충족될 때 함수가 null이 아닌 값을 반환한다는 것을 보장할 수 있어요. 별도의 nullable·non-nullable 함수 오버로드를 하나의 간결한 함수로 대체해 코드를 단순화할 수 있어요.
import kotlin.contracts.*
@OptIn(ExperimentalContracts::class, ExperimentalExtendedContracts::class)
fun decode(encoded: String?): String? {
contract {
// Guarantees a non-null return value when the input is non-null
(encoded != null) implies (returnsNotNull())
}
if (encoded == null) return null
return java.net.URLDecoder.decode(encoded, "UTF-8")
}
fun useDecodedValue(s: String?) {
// Uses a safe call since the return value may be null
decode(s)?.length
if (s != null) {
// Treats the return value as non-null after the smart cast
decode(s).length
}
}
이 예시에서 decode() 함수의 contract는 입력이 null이 아닐 때 반환 값을 스마트 캐스트할 수 있게 해 줘서, 추가 null 검사나 여러 오버로드가 필요 없어져요.
이 기능은 Experimental이에요. 옵트인하려면 build.gradle(.kts) 파일에 다음 컴파일러 옵션을 추가하세요.
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xallow-condition-implies-returns-contracts")
}
}
새 holdsIn 키워드
Kotlin 2.2.20은 contract용 새 holdsIn 키워드를 도입했어요. 특정 람다 안에서 boolean 조건이 true라고 가정된다는 것을 보장하는 데 사용할 수 있어요. 이로써 contract를 사용해 조건부 스마트 캐스트가 있는 DSL을 만들 수 있어요.
예시를 볼게요.
import kotlin.contracts.*
@OptIn(ExperimentalContracts::class, ExperimentalExtendedContracts::class)
fun <T> T.alsoIf(condition: Boolean, block: (T) -> Unit): T {
contract {
// Declares that the lambda runs at most once
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
// Declares that the condition is assumed to be true inside the lambda
condition holdsIn block
}
if (condition) block(this)
return this
}
fun useApplyIf(input: Any) {
val result = listOf(1, 2, 3)
.first()
.alsoIf(input is Int) {
// The input parameter is smart cast to Int inside the lambda
// Prints the sum of input and first list element
println(input + it)
// 2
}
.toString()
}
이 기능은 Experimental이에요. 옵트인하려면 build.gradle(.kts) 파일에 다음 컴파일러 옵션을 추가하세요.
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xallow-holdsin-contract")
}
}
Kotlin/JVM: when 식으로 invokedynamic 지원
Kotlin 2.2.20에서 when 식을 invokedynamic으로 컴파일할 수 있게 됐어요. 이전에는 타입 검사가 여러 개인 when 식이 바이트코드에서 긴 instanceof 검사 체인으로 컴파일됐어요.
이제 다음 조건이 충족되면 when 식에 invokedynamic을 사용해 Java switch 문이 생성하는 것과 유사한 더 작은 바이트코드를 만들 수 있어요.
else를 제외한 모든 조건이is또는null검사인 경우.- 식에 guard conditions(
if)이 포함되지 않은 경우. - 조건에 직접 타입 검사할 수 없는 타입(가변 Kotlin 컬렉션(
MutableList)이나 함수 타입(kotlin.Function1,kotlin.Function2등))이 포함되지 않은 경우. else외에 조건이 최소 두 개 이상인 경우.- 모든 분기가 같은
when식의 subject를 검사하는 경우.
예를 들면 이렇죠.
open class Example
class A : Example()
class B : Example()
class C : Example()
fun test(e: Example) = when (e) {
// Uses invokedynamic with SwitchBootstraps.typeSwitch
is A -> 1
is B -> 2
is C -> 3
else -> 0
}
새 기능을 켜면 이 예시의 when 식은 여러 instanceof 검사 대신 단일 invokedynamic 타입 스위치로 컴파일돼요.
이 기능을 활성화하려면 Kotlin 코드를 JVM 타깃 21 이상으로 컴파일하고 다음 컴파일러 옵션을 추가하세요.
-Xwhen-expressions=indy
또는 build.gradle(.kts) 파일의 compilerOptions {} 블록에 추가해도 돼요.
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xwhen-expressions=indy")
}
}
이 기능은 Experimental이에요. 이슈 트래커 YouTrack에 피드백을 남겨 주시면 감사하겠어요.
Kotlin Multiplatform
Kotlin 2.2.20은 Kotlin Multiplatform에 중요한 변화를 가져와요. Swift export가 기본으로 제공되고, 새 공유 소스 세트가 있으며, 공통 의존성을 관리하는 새 방식을 시도할 수 있어요.
Swift export 기본 제공
Kotlin 2.2.20은 Swift export에 대한 실험적 지원을 도입했어요. Kotlin 소스를 직접 내보내고 Swift에서 Kotlin 코드를 관용적으로 호출할 수 있게 해 주며, Objective-C 헤더가 필요 없어요.
이는 Apple 타깃에서 멀티플랫폼 개발을 크게 개선해요. 예를 들어 최상위 함수가 있는 Kotlin 모듈이 있으면, Swift export는 깔끔하고 모듈 특화된 import를 가능하게 해서 혼란스러운 Objective-C 언더스코어와 복잡한(mangled) 이름을 제거해 줘요.
주요 기능은 다음과 같아요.
- 다중 모듈 지원. 각 Kotlin 모듈이 별도의 Swift 모듈로 내보내져 함수 호출이 단순해져요.
- 패키지 지원. 내보내는 동안 Kotlin 패키지가 명시적으로 보존되어 생성된 Swift 코드에서 이름 충돌을 피해요.
- 타입 별칭. Kotlin 타입 별칭이 Swift로 내보내지고 보존되어 가독성이 좋아져요.
- 원시 타입에 대한 향상된 nullability. nullability를 보존하기 위해
Int?같은 타입을KotlinInt같은 래퍼 클래스로 박싱해야 했던 Objective-C interop과 달리, Swift export는 nullability 정보를 직접 변환해요. - 오버로드. 모호함 없이 Swift에서 Kotlin의 오버로드된 함수를 호출할 수 있어요.
- 평탄화된 패키지 구조. Kotlin 패키지를 Swift enum으로 변환해 생성된 Swift 코드에서 패키지 접두사를 제거할 수 있어요.
- 모듈 이름 커스터마이징. Kotlin 프로젝트의 Gradle 구성에서 결과 Swift 모듈 이름을 커스터마이즈할 수 있어요.
Swift export 활성화 방법
이 기능은 현재 Experimental이며, iOS 프레임워크를 Xcode 프로젝트에 연결하기 위해 직접 통합(direct integration)을 사용하는 프로젝트에서만 동작해요. 이는 IntelliJ IDEA에서 Kotlin Multiplatform 플러그인으로 또는 웹 마법사를 통해 만든 멀티플랫폼 프로젝트의 표준 구성이에요.
Swift export를 시도해 보려면 Xcode 프로젝트를 구성하세요.
- Xcode에서 프로젝트 설정을 여세요.
- Build Phases 탭에서
embedAndSignAppleFrameworkForXcode작업이 있는 Run Script 단계를 찾으세요. - 실행 스크립트 단계에서 대신
embedSwiftExportForXcode작업을 사용하도록 스크립트를 조정하세요.
./gradlew :<Shared module name>:embedSwiftExportForXcode
- 프로젝트를 빌드하세요. 빌드 출력 디렉터리에 Swift 모듈이 생성돼요.
이 기능은 기본으로 제공돼요. 이전 릴리스에서 이미 활성화했다면 이제 gradle.properties 파일에서 kotlin.experimental.swift-export.enabled를 제거할 수 있어요.
시간을 아끼려면 Swift export가 이미 설정된 공개 샘플을 클론해 보세요.
Swift export에 대한 자세한 내용은 문서를 참고해 주세요.
피드백 남기기
향후 Kotlin 릴리스에서 Swift export 지원을 확장하고 점진적으로 안정화할 계획이에요. Kotlin 2.2.20 이후에는 특히 코루틴과 flows를 중심으로 Kotlin과 Swift 사이의 상호 운용성 개선에 집중할 거예요.
Swift export 지원은 Kotlin Multiplatform에 큰 변화예요. 피드백을 보내 주시면 감사하겠어요.
- Kotlin Slack에서 개발 팀에 직접 연락하세요. 초대 받기 후 #swift-export 채널에 참여하세요.
- Swift export에서 겪는 문제는 YouTrack에 보고해 주세요.
js와 wasmJs 타깃을 위한 공유 소스 세트
이전에는 Kotlin Multiplatform에 JavaScript(js)와 WebAssembly(wasmJs) 웹 타깃을 위한 공유 소스 세트가 기본으로 포함되지 않았어요. js와 wasmJs 사이에 코드를 공유하려면 커스텀 소스 세트를 수동으로 구성하거나, 코드를 두 곳에 — js용 버전 하나와 wasmJs용 버전 하나 — 작성해야 했어요. 예를 들면 이렇죠.
// commonMain
expect suspend fun readCopiedText(): String
// jsMain
external interface Navigator { val clipboard: Clipboard }
// Different interop in JS and Wasm
external interface Clipboard { fun readText(): Promise<String> }
external val navigator: Navigator
suspend fun readCopiedText(): String {
// Different interop in JS and Wasm
return navigator.clipboard.readText().await()
}
// wasmJsMain
external interface Navigator { val clipboard: Clipboard }
external interface Clipboard { fun readText(): Promise<JsString> }
external val navigator: Navigator
suspend fun readCopiedText(): String {
return navigator.clipboard.readText().await().toString()
}
이번 릴리스부터 Kotlin Gradle 플러그인은 기본 계층 템플릿을 사용할 때 웹용 새 공유 소스 세트(webMain과 webTest로 구성)를 추가해요.
이 변경으로 web 소스 세트가 js와 wasmJs 소스 세트 양쪽의 부모가 돼요. 업데이트된 소스 세트 계층은 다음과 같아요.
새 소스 세트를 쓰면 js와 wasmJs 타깃 양쪽을 위한 코드를 한 번에 작성할 수 있어요. 공유 코드를 webMain에 두면 양쪽에서 자동으로 동작해요.
// commonMain
expect suspend fun readCopiedText(): String
// webMain
@OptIn(ExperimentalWasmJsInterop::class)
private suspend fun <R : JsAny?> Promise<R>.await(): R = suspendCancellableCoroutine { continuation ->
this.then(
onFulfilled = { continuation.resumeWith(Result.success(it)); null },
onRejected = { continuation.resumeWithException(it.asJsException()); null }
)
}
external interface Navigator { val clipboard: Clipboard }
external interface Clipboard { fun readText(): Promise<JsString> }
external val navigator: Navigator
actual suspend fun readCopiedText(): String {
return navigator.clipboard.readText().await().toString()
}
이 업데이트는 js와 wasmJs 타깃 사이의 코드 공유를 단순화해요. 특히 다음 두 경우에 유용해요.
- 라이브러리 작성자라면, 코드를 중복하지 않고
js와wasmJs타깃 양쪽을 지원하고 싶을 때. - 웹을 타깃으로 하는 Compose Multiplatform 애플리케이션을 개발하면서, 더 넓은 브라우저 호환성을 위해
js와wasmJs타깃 양쪽의 크로스 컴파일을 활성화하고 싶을 때. 이 폴백 모드 덕분에 웹사이트를 만들면 모든 브라우저에서 바로 동작해요. 최신 브라우저는wasmJs를, 구형 브라우저는js를 사용하기 때문이에요.
이 기능을 시도하려면 build.gradle(.kts) 파일의 kotlin {} 블록에서 기본 계층 템플릿을 사용하세요.
kotlin {
js()
wasmJs()
// Enables the default source set hierarchy, including webMain and webTest
applyDefaultHierarchyTemplate()
}
기본 계층을 사용하기 전에, 커스텀 공유 소스 세트가 있는 프로젝트이거나 js("web")` 타깃 이름을 바꾼 경우 발생할 수 있는 잠재적 충돌을 신중히 고려하세요. 충돌을 해결하려면 충돌하는 소스 세트나 타깃의 이름을 바꾸거나, 기본 계층을 사용하지 마세요.
Kotlin 라이브러리의 Stable 크로스 플랫폼 컴파일
Kotlin 2.2.20은 Kotlin 라이브러리의 크로스 플랫폼 컴파일을 안정화하는 중요한 로드맵 항목을 완료했어요.
이제 Kotlin 라이브러리를 게시하기 위한 .klib 아티팩트를 만들 때 지원되는 호스트를 모두 사용할 수 있어요. 이는 특히 이전에 Mac 기기가 필요했던 Apple 타깃에서 게시 과정을 크게 간소화해요.
이 기능은 기본으로 제공돼요. kotlin.native.enableKlibsCrossCompilation=true로 이미 크로스 컴파일을 활성화했다면 이제 gradle.properties 파일에서 제거할 수 있어요.
아쉽게도 아직 몇 가지 제한이 남아 있어요. 다음 경우에는 여전히 Mac 기기를 사용해야 해요.
- 라이브러리나 의존 모듈에 cinterop 의존성이 있는 경우.
- 프로젝트에 CocoaPods 통합이 설정된 경우.
- Apple 타깃용 최종 바이너리를 빌드하거나 테스트해야 하는 경우.
멀티플랫폼 라이브러리의 게시에 대한 자세한 내용은 문서를 참고해 주세요.
공통 의존성을 선언하는 새 방식
Gradle로 멀티플랫폼 프로젝트를 설정하는 것을 단순화하기 위해, Kotlin 2.2.20은 프로젝트가 Gradle 8.8 이상을 사용할 때 kotlin {} 블록 안의 최상위 dependencies {} 블록으로 공통 의존성을 선언할 수 있게 해 줘요. 이 의존성들은 commonMain 소스 세트에 선언된 것처럼 동작해요. 이 기능은 Kotlin/JVM과 Android 전용 프로젝트에서 쓰는 dependencies 블록과 비슷하게 동작하며, Kotlin Multiplatform에서는 이제 Experimental이에요.
프로젝트 레벨에서 공통 의존성을 선언하면 소스 세트 간의 반복 구성을 줄이고 빌드 설정을 간소화할 수 있어요. 필요에 따라 각 소스 세트에 플랫폼 전용 의존성을 여전히 추가할 수 있어요.
이 기능을 시도하려면 최상위 dependencies {} 블록 앞에 @OptIn(ExperimentalKotlinGradlePluginApi::class) 어노테이션을 추가해 옵트인하세요. 예를 들면 이렇죠.
kotlin {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0")
}
}
이 기능에 대한 피드백은 YouTrack에 남겨 주시면 감사하겠어요.
의존성의 타깃 지원에 대한 새 진단
Kotlin 2.2.20 이전에는 빌드 스크립트의 의존성이 소스 세트가 요구하는 모든 타깃을 지원하지 않을 때, Gradle이 생성하는 오류 메시지로 문제를 이해하기 어려웠어요.
Kotlin 2.2.20은 각 의존성이 어떤 타깃을 지원하고 어떤 타깃을 지원하지 않는지 명확히 보여 주는 새 진단을 도입했어요.
이 진단은 기본으로 활성화돼요. 어떤 이유로 비활성화해야 한다면 이 YouTrack 이슈에 댓글로 알려 주세요. gradle.properties 파일에서 다음 Gradle 프로퍼티로 진단을 비활성화할 수 있어요.
| 프로퍼티 | 설명 |
|---|---|
| kotlin.kmp.eagerUnresolvedDependenciesDiagnostic=false | 메타데이터 컴파일과 import에 대해서만 진단을 실행해요 |
| kotlin.kmp.unresolvedDependenciesDiagnostic=false | 진단을 완전히 비활성화해요 |
Kotlin/Native
이번 릴리스는 Xcode 26 지원, Objective-C/Swift와의 상호 운용성 개선, 디버깅 개선, 새 바이너리 옵션을 가져와요.
Xcode 26 지원
Kotlin 2.2.21부터 Kotlin/Native 컴파일러는 Xcode 26 — 최신 안정 버전의 Xcode — 을 지원해요. 이제 Xcode를 업데이트하고 최신 API에 접근해 Apple 운영 체제용 Kotlin 프로젝트를 계속 작업할 수 있어요.
바이너리의 스택 캐너리 지원
Kotlin 2.2.20부터 Kotlin은 결과 Kotlin/Native 바이너리에 스택 캐너리(stack canary)를 지원해요. 스택 보호의 일부인 이 보안 기능은 스택 스매싱(stack smashing)을 방어해 일반적인 애플리케이션 취약점을 완화해요. Swift와 Objective-C에서 이미 사용 가능했으며, 이제 Kotlin에서도 지원돼요.
Kotlin/Native의 스택 보호 구현은 Clang의 스택 보호자 동작을 따릅니다.
스택 캐너리를 활성화하려면 gradle.properties 파일에 다음 바이너리 옵션을 추가하세요.
kotlin.native.binary.stackProtector=yes
이 프로퍼티는 스택 스매싱에 취약한 모든 Kotlin 함수에 기능을 활성화해요. 다른 모드는 다음과 같아요.
kotlin.native.binary.stackProtector=strong: 스택 스매싱에 취약한 함수에 더 강한 휴리스틱을 사용해요.kotlin.native.binary.stackProtector=all: 모든 함수에 스택 보호자를 활성화해요.
어떤 경우엔 스택 보호와 함께 성능 비용이 발생할 수 있다는 점을 유의하세요.
릴리스 바이너리의 더 작은 바이너리 크기
Kotlin 2.2.20은 릴리스 바이너리의 크기를 줄이는 데 도움이 되는 smallBinary 옵션을 도입했어요. 새 옵션은 LLVM 컴파일 단계에서 컴파일러의 기본 최적화 인자로 -Oz를 효과적으로 설정해요.
smallBinary 옵션을 켜면 릴리스 바이너리를 더 작게 만들고 빌드 시간을 개선할 수 있어요. 다만 어떤 경우에는 런타임 성능에 영향을 줄 수 있어요.
이 새 기능은 현재 Experimental이에요. 프로젝트에서 시도해 보려면 gradle.properties 파일에 다음 바이너리 옵션을 추가하세요.
kotlin.native.binary.smallBinary=true
Kotlin 팀은 이 기능을 구현하는 데 도움을 준 Troels Lund에게 감사하고 있어요.
개선된 디버거 객체 요약
Kotlin/Native는 이제 LLDB와 GDB 같은 디버거 도구를 위해 더 명확한 객체 요약을 생성해요. 이로써 생성되는 디버그 정보의 가독성이 좋아지고 디버깅 경험이 개선돼요.
예를 들어 다음 객체를 생각해 볼게요.
class Point(val x: Int, val y: Int)
val point = Point(1, 2)
이전에는 검사 시 객체의 메모리 주소에 대한 포인터를 포함한 제한된 정보만 보였어요.
(lldb) v point
(ObjHeader *) point = [x: ..., y: ...]
(lldb) v point->x
(int32_t *) x = 0x0000000100274048
Kotlin 2.2.20에서는 디버거가 실제 값을 포함한 더 풍부한 세부 사항을 보여 줘요.
(lldb) v point
(ObjHeader *) point = Point(x=1, y=2)
(lldb) v point->x
(int32_t) point->x = 1
Kotlin 팀은 이 기능을 구현하는 데 도움을 준 Nikita Nazarov에게 감사하고 있어요.
Kotlin/Native 디버깅에 대한 자세한 내용은 문서를 참고해 주세요.
Objective-C 헤더의 블록 타입에 명시적 이름
Kotlin 2.2.20은 Kotlin/Native 프로젝트에서 내보낸 Objective-C 헤더의 Kotlin 함수 타입에 명시적 파라미터 이름을 추가하는 옵션을 도입했어요. 파라미터 이름은 Xcode의 자동 완성 제안을 개선하고 Clang 경고를 피하는 데 도움이 돼요.
이전에는 생성된 Objective-C 헤더에서 블록 타입의 파라미터 이름이 생략됐어요. 그런 경우 Xcode의 자동 완성은 Objective-C 블록에서 파라미터 이름 없이 함수를 호출하도록 제안하곤 했어요. 생성된 블록은 Clang 경고를 발생시켰어요.
예를 들어 다음 Kotlin 코드에 대해:
// Kotlin:
fun greetUser(block: (name: String) -> Unit) = block("John")
생성된 Objective-C 헤더에는 파라미터 이름이 없었어요.
// Objective-C:
+ (void)greetUserBlock:(void (^)(NSString *))block __attribute__((swift_name("greetUser(block:)")));
그래서 Xcode에서 Objective-C로 greetUserBlock() 함수를 호출할 때 IDE는 다음과 같이 제안했어요.
// Objective-C:
greetUserBlock:^(NSString *) {
// ...
};
제안에서 파라미터 이름 (NSString *)이 빠져 있어서 Clang 경고가 발생했어요.
새 옵션을 쓰면 Kotlin이 Kotlin 함수 타입의 파라미터 이름을 Objective-C 블록 타입으로 전달해서, Xcode가 제안에서 이를 사용해요.
// Objective-C:
greetUserBlock:^(NSString *name) {
// ...
};
명시적 파라미터 이름을 활성화하려면 gradle.properties 파일에 다음 바이너리 옵션을 추가하세요.
kotlin.native.binary.objcExportBlockExplicitParameterNames=true
Kotlin 팀은 이 기능을 구현한 Yijie Jiang에게 감사하고 있어요.
Kotlin/Native 배포 크기 축소
Kotlin/Native 배포에는 컴파일러 코드가 포함된 JAR 파일 두 개가 들어 있었어요.
konan/lib/kotlin-native.jarkonan/lib/kotlin-native-compiler-embeddable.jar.
Kotlin 2.2.20부터 kotlin-native.jar는 더 이상 게시되지 않아요.
제거된 JAR 파일은 더 이상 필요 없는 레거시 버전의 임베더블(embeddable) 컴파일러예요. 이 변경으로 배포 크기가 크게 줄었어요.
그 결과 다음 옵션은 이제 deprecated되고 제거됐어요.
kotlin.native.useEmbeddableCompilerJar=falseGradle 프로퍼티. 대신 Kotlin/Native 프로젝트에서는 항상 임베더블 컴파일러 JAR 파일이 사용돼요.KotlinCompilerPluginSupportPlugin.getPluginArtifactForNative()함수. 대신getPluginArtifact()함수가 항상 사용돼요.
자세한 내용은 YouTrack 이슈를 참고해 주세요.
KDocs를 Objective-C 헤더로 기본 내보내기
Kotlin/Native 최종 바이너리 컴파일 중 Objective-C 헤더를 생성할 때 KDoc 주석이 이제 기본으로 내보내져요.
이전에는 빌드 파일에 -Xexport-kdoc 옵션을 수동으로 추가해야 했어요. 이제 컴파일 작업에 자동으로 전달돼요.
이 옵션은 KDoc 주석을 klib에 포함시키고, Apple 프레임워크를 만들 때 klib에서 주석을 추출해요. 그 결과 클래스와 메서드의 주석이 예를 들어 Xcode의 자동 완성에 나타나요.
build.gradle(.kts) 파일의 binaries {} 블록에서 klib에서 생성된 Apple 프레임워크로 KDoc 주석이 내보내지는 것을 비활성화할 수 있어요.
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
kotlin {
iosArm64 {
binaries {
framework {
baseName = "sdk"
@OptIn(ExperimentalKotlinGradlePluginApi::class)
exportKdoc.set(false)
}
}
}
}
자세한 내용은 문서를 참고해 주세요.
x86_64 Apple 타깃 deprecation
Apple은 몇 년 전에 Intel 칩이 장착된 기기 생산을 중단했고, 최근 발표에 따르면 macOS Tahoe 26이 Intel 기반 아키텍처를 지원하는 마지막 OS 버전이 될 거예요.
이로 인해 특히 macOS 26과 함께 제공되는 지원 Xcode 버전을 업데이트할 향후 Kotlin 릴리스에서, 빌드 에이전트에서 이 타깃들을 제대로 테스트하기가 점점 어려워지고 있어요.
Kotlin 2.2.20부터 macosX64와 iosX64 타깃은 지원 티어 2로 격하됐어요. 즉 이 타깃은 컴파일 여부를 확인하기 위해 CI에서 정기적으로 테스트되지만, 실행 여부를 확인하기 위한 자동 테스트는 되지 않을 수 있어요.
Kotlin 2.2.20−2.4.0 릴리스 주기 동안 모든 x86_64 Apple 타깃을 점진적으로 deprecated하고 결국 지원을 제거할 계획이에요. 여기에는 다음 타깃이 포함돼요.
macosX64iosX64tvosX64watchosX64
지원 티어에 대한 자세한 내용은 Kotlin/Native 타깃 지원을 참고해 주세요.
Kotlin/Wasm
Kotlin/Wasm이 이제 Beta가 되어 더 큰 안정성을 제공하고, npm 의존성 분리, JavaScript interop을 위한 개선된 예외 처리, 브라우저 디버깅 기본 지원 등의 개선을 포함해요.
npm 의존성 분리
이전에는 Kotlin/Wasm 프로젝트에서 모든 npm 의존성이 Kotlin 도구 의존성과 여러분의 의존성 양쪽 모두 프로젝트 폴더에 함께 설치됐어요. 또한 프로젝트의 잠금 파일(package-lock.json 또는 yarn.lock)에도 함께 기록됐어요.
그 결과 Kotlin 도구 의존성이 업데이트될 때마다, 여러분이 아무것도 추가하거나 변경하지 않아도 잠금 파일을 업데이트해야 했어요.
Kotlin 2.2.20부터 Kotlin 도구 npm 의존성은 프로젝트 밖에 설치돼요. 이제 도구 의존성과 여러분의(사용자) 의존성은 별도의 디렉터리를 가져요.
-
도구 의존성 디렉터리:
<
kotlin-user-home>/kotlin-npm-tooling/<yarn|npm>/hash/node_modules -
사용자 의존성 디렉터리:
build/wasm/node_modules
추가로 프로젝트 디렉터리 안의 잠금 파일에는 사용자 정의 의존성만 포함돼요.
이 개선은 잠금 파일을 여러분의 의존성에만 집중되게 유지하고, 더 깔끔한 프로젝트를 유지하는 데 도움이 되며, 파일의 불필요한 변경을 줄여 줘요.
이 변경은 wasm-js 타깃에서 기본으로 활성화돼요. js 타깃에는 아직 구현되지 않았어요. 향후 릴리스에서 구현할 계획이 있지만, Kotlin 2.2.20에서 js 타깃의 npm 의존성 동작은 이전과 동일해요.
Kotlin/Wasm과 JavaScript interop의 예외 처리 개선
이전에는 JavaScript(JS)에서 던져져 Kotlin/Wasm 코드로 넘어오는 예외(오류)를 Kotlin이 이해하기 어려웠어요.
어떤 경우에는 반대 방향에서도 문제가 발생했어요. 예외가 Wasm 코드에서 JS로 던져지거나 전달될 때 세부 정보 없이 WebAssembly.Exception으로 감싸이는 경우였죠. 이런 Kotlin 예외 처리 문제로 디버깅이 어려웠어요.
Kotlin 2.2.20부터 예외 처리의 개발자 경험이 양방향 모두 개선돼요.
- JS에서 예외가 던져지면 Kotlin 쪽에서 더 많은 정보를 볼 수 있어요. 그런 예외가 Kotlin을 통해 JS로 다시 전파될 때 더 이상 WebAssembly로 감싸지지 않아요.
- Kotlin에서 예외가 던져지면 이제 JS 쪽에서 JS 오류로 catch할 수 있어요.
새 예외 처리는 WebAssembly.JSTag 기능을 지원하는 최신 브라우저에서 자동으로 동작해요.
- Chrome 115+
- Firefox 129+
- Safari 18.4+
구형 브라우저에서는 예외 처리 동작이 변경되지 않아요.
구성 없이 브라우저에서 디버깅 지원
이전에는 브라우저가 디버깅에 필요한 Kotlin/Wasm 프로젝트 소스에 자동으로 접근할 수 없었어요. 브라우저에서 Kotlin/Wasm 애플리케이션을 디버깅하려면 build.gradle(.kts) 파일에 다음 스니펫을 추가해 이 소스들을 제공하도록 빌드를 수동으로 구성해야 했어요.
devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply {
static = (static ?: mutableListOf()).apply {
add(project.rootDir.path)
}
}
Kotlin 2.2.20부터 최신 브라우저에서 애플리케이션을 디버깅하는 것이 기본으로 동작해요. Gradle 개발 작업(*DevRun)을 실행하면 Kotlin이 자동으로 소스 파일을 브라우저에 제공해서, 추가 설정 없이 중단점 설정, 변수 검사, Kotlin 코드 단계별 실행을 할 수 있어요.
이 변경은 수동 구성을 없애 디버깅을 단순화해요. 필요한 구성은 이제 Kotlin Gradle 플러그인에 포함돼 있어요. 이전에 이 구성을 build.gradle(.kts) 파일에 추가했다면 충돌을 피하기 위해 제거해야 해요.
브라우저 디버깅은 모든 Gradle *DevRun 작업에서 기본으로 활성화돼요. 이 작업들은 애플리케이션뿐 아니라 소스 파일도 제공하므로, 로컬 개발에만 사용하고 소스가 공개적으로 노출될 수 있는 클라우드나 프로덕션 환경에서는 실행하지 마세요.
디버깅 중 반복 다시 로드 처리
소스를 기본으로 제공하면 Kotlin 컴파일과 번들링이 완료되기 전에 브라우저에서 애플리케이션이 반복적으로 다시 로드될 수 있어요. 우회책으로, Kotlin 소스 파일을 무시하고 제공되는 정적 파일에 대한 감시를 비활성화하도록 webpack 구성을 조정하세요. 프로젝트 루트의 webpack.config.d 디렉터리에 다음 내용의 .js 파일을 추가하세요.
config.watchOptions = config.watchOptions || {
ignored: ["**/*.kt", "**/node_modules"]
}
if (config.devServer) {
config.devServer.static = config.devServer.static.map(file => {
if (typeof file === "string") {
return { directory: file,
watch: false,
}
} else {
return file
}
})
}
빈 yarn.lock 파일 제거
이전에는 Kotlin Gradle 플러그인(KGP)이 Kotlin 도구 체인에 필요한 npm 패키지에 대한 정보와, 프로젝트 또는 사용된 라이브러리의 기존 npm 의존성을 포함하는 yarn.lock 파일을 자동으로 생성했어요.
이제 KGP는 도구 체인 의존성을 별도로 관리하며, 프로젝트에 npm 의존성이 없는 한 프로젝트 레벨 yarn.lock 파일이 더 이상 생성되지 않아요.
KGP는 npm 의존성이 추가되면 yarn.lock 파일을 자동으로 만들고, npm 의존성이 제거되면 yarn.lock 파일을 삭제해요.
이 변경은 프로젝트 구조를 정리하고 실제 npm 의존성이 언제 도입되는지 추적하기 쉽게 해 줘요.
이 동작을 구성하기 위한 추가 단계는 필요 없어요. Kotlin 2.2.20부터 Kotlin/Wasm 프로젝트에서 기본으로 적용돼요.
정규화된 클래스 이름의 새 컴파일러 오류
Kotlin/Wasm에서 컴파일러는 기본적으로 생성된 바이너리에 클래스의 정규화된 이름(FQN)을 저장하지 않아요. 이 접근은 애플리케이션 크기 증가를 피하기 위한 것이에요.
그 결과 이전 Kotlin 릴리스에서는 KClass::qualifiedName 프로퍼티를 호출하면 클래스의 정규화된 이름 대신 빈 문자열이 반환됐어요.
Kotlin 2.2.20부터 컴파일러는 정규화된 이름 기능을 명시적으로 활성화하지 않는 한, Kotlin/Wasm 프로젝트에서 KClass::qualifiedName 프로퍼티를 사용하면 오류를 보고해요.
이 변경은 qualifiedName 프로퍼티 호출 시 예상치 못한 빈 문자열을 방지하고, 컴파일 타임에 문제를 잡아 개발자 경험을 개선해요.
이 진단은 기본으로 활성화되며 오류가 자동으로 보고돼요. 진단을 비활성화하고 Kotlin/Wasm에서 FQN 저장을 허용하려면 build.gradle(.kts) 파일에 다음 옵션을 추가해 모든 클래스의 정규화된 이름을 저장하도록 컴파일러에 지시하세요.
kotlin {
wasmJs {
...
compilerOptions {
freeCompilerArgs.add("-Xwasm-kclass-fqn")
}
}
}
이 옵션을 활성화하면 애플리케이션 크기가 커진다는 점을 기억하세요.
Kotlin/JS
Kotlin 2.2.20은 BigInt 타입으로 Kotlin의 Long 타입을 표현하는 것을 지원해서 내보낸 선언에서 Long을 사용할 수 있게 해 줘요. 또한 이번 릴리스는 Node.js 인자를 정리하는 DSL 함수를 추가했어요.
Kotlin의 Long 타입을 표현하기 위한 BigInt 타입 사용
ES2020 표준 이전에는 JavaScript(JS)가 53비트보다 큰 정밀 정수를 위한 원시 타입을 지원하지 않았어요.
이런 이유로 Kotlin/JS는 Long 값(64비트)을 두 개의 number 프로퍼티를 가진 JavaScript 객체로 표현했어요. 이 커스텀 구현은 Kotlin과 JavaScript 사이의 상호 운용성을 더 복잡하게 만들었어요.
Kotlin 2.2.20부터 modern JavaScript(ES2020)로 컴파일할 때 Kotlin/JS는 Kotlin의 Long 값을 JavaScript에 내장된 BigInt 타입으로 표현해요.
이 변경은 Kotlin 2.2.20에서 도입된 Long 타입을 JavaScript로 내보내기를 가능하게 해요. 그 결과 Kotlin과 JavaScript 사이의 상호 운용성이 단순해져요.
활성화하려면 build.gradle(.kts) 파일에 다음 컴파일러 옵션을 추가해야 해요.
kotlin {
js {
...
compilerOptions {
freeCompilerArgs.add("-Xes-long-as-bigint")
}
}
}
이 기능은 Experimental이에요. 이슈 트래커 YouTrack에 피드백을 남겨 주시면 감사하겠어요.
내보낸 선언에서 Long 사용
Kotlin/JS가 커스텀 Long 표현을 사용했기 때문에 JavaScript에서 Kotlin의 Long과 상호 작용할 수 있는 직관적인 방법을 제공하기 어려웠어요. 그 결과 Long 타입을 사용하는 Kotlin 코드를 JavaScript로 내보낼 수 없었어요. 이 문제는 함수 파라미터, 클래스 프로퍼티, 생성자 같은 Long을 사용하는 모든 코드에 영향을 줬어요.
이제 Kotlin의 Long 타입을 JavaScript의 BigInt 타입으로 컴파일할 수 있으므로, Kotlin/JS는 Long 값을 JavaScript로 내보내기를 지원해서 Kotlin과 JavaScript 코드 사이의 상호 운용성을 단순화해요.
이 기능을 활성화하려면:
build.gradle(.kts)파일의freeCompilerArgs속성에 다음 컴파일러 옵션을 추가해 Kotlin/JS에서Long내보내기를 허용하세요.
kotlin {
js {
...
compilerOptions {
freeCompilerArgs.add("-XXLanguage:+JsAllowLongInExportedDeclarations")
}
}
}
BigInt타입을 활성화하세요. 활성화 방법은 Kotlin의Long타입을 표현하기 위한BigInt타입 사용을 참고하세요.
더 깔끔한 인자를 위한 새 DSL 함수
Node.js로 Kotlin/JS 애플리케이션을 실행할 때, 프로그램에 전달되는 인자(args)는 이전에 다음을 포함했어요.
- 실행 파일
Node의 경로. - 스크립트의 경로.
- 여러분이 제공한 실제 명령줄 인자.
하지만 args의 기대 동작은 명령줄 인자만 포함하는 것이었어요. 이를 위해 build.gradle(.kts) 파일이나 Kotlin 코드에서 drop() 함수를 사용해 처음 두 인자를 수동으로 건너뛰어야 했어요.
fun main(args: Array<String>) {
println(args.drop(2).joinToString(", "))
}
이 우회책은 반복적이고 오류가 나기 쉬우며, 플랫폼 간에 코드를 공유할 때 잘 동작하지 않았어요.
이 문제를 해결하기 위해 Kotlin 2.2.20은 passCliArgumentsToMainFunction()이라는 새 DSL 함수를 도입했어요.
이 함수를 쓰면 명령줄 인자만 포함되고 Node와 스크립트 경로는 제외돼요.
fun main(args: Array<String>) {
// No need for drop() and only your custom arguments are included
println(args.joinToString(", "))
}
이 변경은 상용구 코드를 줄이고, 인자를 수동으로 버리면서 생기는 실수를 방지하며, 크로스 플랫폼 호환성을 개선해요.
이 기능을 활성화하려면 build.gradle(.kts) 파일 안에 다음 DSL 함수를 추가하세요.
kotlin {
js {
nodejs {
passCliArgumentsToMainFunction()
}
}
}
Gradle
Kotlin 2.2.20은 Gradle 빌드 보고서의 Kotlin/Native 작업에 새 컴파일러 성능 지표를 추가하고, 증분 컴파일의 품질 개선을 가져와요.
Kotlin/Native 작업을 위한 빌드 보고서의 새 컴파일러 성능 지표
Kotlin 1.7.0에서 컴파일러 성능을 추적하는 데 도움이 되는 빌드 보고서를 도입했어요. 그 이후로 성능 문제를 조사할 때 보고서를 더 상세하고 유용하게 만들기 위해 지표를 더 추가했어요.
Kotlin 2.2.20에서 빌드 보고서는 이제 Kotlin/Native 작업에 대한 컴파일러 성능 지표를 포함해요.
빌드 보고서와 구성 방법에 대한 자세한 내용은 빌드 보고서 활성화를 참고해 주세요.
Kotlin/JVM 개선된 증분 컴파일 미리 보기
Kotlin 2.0.0은 최적화된 프론트엔드를 가진 새 K2 컴파일러를 도입했어요. Kotlin 2.2.20은 이 위에 새 프론트엔드를 사용해 Kotlin/JVM의 특정 복잡한 증분 컴파일 시나리오에서 성능을 개선해요.
동작을 안정화하는 동안 이러한 개선은 기본으로 비활성화돼 있어요. 활성화하려면 gradle.properties 파일에 다음 프로퍼티를 추가하세요.
kotlin.incremental.jvm.fir=true
현재 kapt 컴파일러 플러그인은 이 새 동작과 호환되지 않아요. 향후 Kotlin 릴리스에서 지원을 추가하는 작업을 진행 중이에요.
이 기능에 대한 피드백은 YouTrack에서 주시면 감사하겠어요.
증분 컴파일이 inline 함수의 람다 변경 감지
Kotlin 2.2.20 이전에는 증분 컴파일을 활성화하고 inline 함수의 람다 안 로직을 변경해도, 컴파일러가 다른 모듈에 있는 해당 inline 함수의 호출 지점을 다시 컴파일하지 않았어요. 그 결과 호출 지점은 이전 버전의 람다를 사용해 예기치 않은 동작이 발생할 수 있었어요.
Kotlin 2.2.20에서 컴파일러는 inline 함수의 람다 변경을 감지하고 해당 호출 지점을 자동으로 다시 컴파일해요.
라이브러리 게시 개선
Kotlin 2.2.20은 라이브러리 게시를 더 쉽게 만드는 새 Gradle 작업을 추가해요. 이 작업들은 키 쌍 생성, 공개 키 업로드, 로컬 검사 실행을 도와서 Maven Central 저장소에 업로드하기 전에 검증 과정이 성공하는지 확인할 수 있게 해 줘요.
게시 과정의 일부로 이 작업들을 사용하는 방법에 대한 자세한 내용은 라이브러리를 Maven Central에 게시를 참고해 주세요.
PGP 키 생성·업로드를 위한 새 Gradle 작업
Kotlin 2.2.20 이전에는 멀티플랫폼 라이브러리를 Maven Central 저장소에 게시하려면, 게시물에 서명할 키 쌍을 생성하기 위해 gpg 같은 타사 프로그램을 설치해야 했어요. 이제 Kotlin Gradle 플러그인에 키 쌍을 생성하고 공개 키를 업로드할 수 있는 Gradle 작업이 포함되어 다른 프로그램을 설치할 필요가 없어요.
키 쌍 생성
generatePgpKeys 작업은 키 쌍을 생성해요. 실행할 때 개인 키 저장소의 비밀번호와 이름을 다음 형식으로 제공해야 해요.
./gradlew -Psigning.password=example-password generatePgpKeys --name "John Smith <[email protected]>"
이 작업은 키 쌍을 build/pgp 디렉터리에 저장해요.
실수로 삭제되거나 무단 접근되지 않도록 키 쌍을 안전한 위치로 옮기세요.
공개 키 업로드
uploadPublicPgpKey 작업은 공개 키를 Ubuntu의 키 서버 keyserver.ubuntu.com에 업로드해요. 실행할 때 .asc 형식의 공개 키 경로를 제공하세요.
./gradlew uploadPublicPgpKey --keyring /path_to/build/pgp/public_KEY_ID.asc
로컬에서 검증을 테스트하는 새 Gradle 작업
Kotlin 2.2.20은 라이브러리를 Maven Central 저장소에 업로드하기 전에 검증을 로컬에서 테스트하는 Gradle 작업도 추가해요.
Kotlin Gradle 플러그인을 Gradle의 Signing Plugin과 Maven Publish Plugin과 함께 사용한다면, checkSigningConfiguration과 checkPomFileFor<PUBLICATION_NAME>Publication 작업을 실행해 설정이 Maven Central의 요구 사항을 충족하는지 확인할 수 있어요. <PUBLICATION_NAME>을 여러분의 게시물 이름으로 바꾸세요.
이 작업들은 build나 check Gradle 작업의 일부로 자동 실행되지 않으므로 직접 실행해야 해요. 예를 들어 KotlinMultiplatform 게시물이 있다면:
./gradlew checkSigningConfiguration checkPomFileForKotlinMultiplatformPublication
checkSigningConfiguration 작업은 다음을 확인해요.
- Signing Plugin에 키가 구성되어 있는지.
- 구성된 공개 키가
keyserver.ubuntu.com또는keys.openpgp.org키 서버에 업로드되었는지. - 모든 게시물에 서명이 활성화되어 있는지.
이 검사 중 하나라도 실패하면 작업은 문제를 해결하는 방법에 대한 정보와 함께 오류를 반환해요.
checkPomFileFor<PUBLICATION_NAME>Publication 작업은 pom.xml 파일이 Maven Central의 요구 사항을 충족하는지 확인해요. 충족하지 않으면 작업은 pom.xml 파일의 어떤 부분이 기준에 미달인지에 대한 세부 정보와 함께 오류를 반환해요.
Maven: kotlin-maven-plugin에서 Kotlin 데몬 지원
Kotlin 2.2.20은 Kotlin 2.2.0에서 도입된 빌드 도구 API를 한 단계 더 발전시켜 kotlin-maven-plugin에서 Kotlin 데몬을 지원해요. Kotlin 데몬을 사용하면 Kotlin 컴파일러가 별도의 격리된 프로세스에서 실행되어, 다른 Maven 플러그인이 시스템 프로퍼티를 덮어쓰는 것을 방지해요. 예시는 이 YouTrack 이슈에서 볼 수 있어요.
Kotlin 2.2.20부터 Kotlin 데몬이 기본으로 사용돼요. 이전 동작으로 되돌리려면 pom.xml 파일에 다음 프로퍼티를 false로 설정해 옵트아웃하세요.
<properties>
<kotlin.compiler.daemon>false</kotlin.compiler.daemon>
</properties>
Kotlin 2.2.20은 또한 Kotlin 데몬의 기본 JVM 인자를 커스터마이즈하는 데 사용할 수 있는 새 jvmArgs 프로퍼티를 도입했어요. 예를 들어 -Xmx와 -Xms 옵션을 덮어쓰려면 pom.xml 파일에 다음을 추가하세요.
<properties>
<kotlin.compiler.daemon.jvmArgs>Xmx1500m,Xms500m</kotlin.compiler.daemon.jvmArgs>
</properties>
Kotlin 컴파일러 옵션의 새 공통 스키마
Kotlin 2.2.20은 org.jetbrains.kotlin:kotlin-compiler-arguments-description으로 게시되는 모든 컴파일러 옵션에 대한 공통 스키마를 도입했어요. 이 아티팩트는 모든 컴파일러 옵션과 그 설명, 그리고 각 옵션이 도입되거나 안정화된 버전 같은 메타데이터의 코드 표현과 (비-JVM 소비자용) JSON 등가물을 모두 포함해요. 이 스키마를 사용해 옵션의 커스텀 뷰를 생성하거나 필요에 따라 분석할 수 있어요.
표준 라이브러리
이번 릴리스는 표준 라이브러리에 새 실험적 기능을 도입해요. Kotlin/JS에서 인터페이스 타입을 식별하기 위한 리플렉션 지원, 공통 원자 타입용 업데이트 함수, 배열 크기 조정용 copyOf() 오버로드가 있어요.
Kotlin/JS에서 리플렉션으로 인터페이스 타입 식별 지원
Kotlin 2.2.20은 Experimental KClass.isInterface 프로퍼티를 Kotlin/JS 표준 라이브러리에 추가했어요.
이 프로퍼티로 클래스 참조가 Kotlin 인터페이스를 나타내는지 확인할 수 있어요. 이로써 Kotlin/JS가 KClass.java.isInterface를 사용해 클래스가 인터페이스를 나타내는지 확인할 수 있는 Kotlin/JVM과 더 가까워져요.
옵트인하려면 @OptIn(ExperimentalStdlibApi::class) 어노테이션을 사용하세요.
@OptIn(ExperimentalStdlibApi::class)
fun inspect(klass: KClass<*>) {
// Prints true for interfaces
println(klass.isInterface)
}
이슈 트래커 YouTrack에 피드백을 남겨 주시면 감사하겠어요.
공통 원자 타입의 새 업데이트 함수
Kotlin 2.2.20은 공통 원자 타입과 그 배열 대응 요소를 업데이트하기 위한 새 실험적 함수를 도입했어요. 각 함수는 업데이트 함수 중 하나를 사용해 새 값을 원자적으로 계산하고 현재 값을 대체하며, 반환 값은 사용하는 함수에 따라 달라져요.
update()와updateAt()은 결과를 반환하지 않고 새 값을 설정해요.fetchAndUpdate()와fetchAndUpdateAt()은 새 값을 설정하고 변경 전의 이전 값을 반환해요.updateAndFetch()와updateAndFetchAt()은 새 값을 설정하고 변경 후의 업데이트된 값을 반환해요.
이 함수들을 사용해 곱셈이나 비트 연산 같은 기본으로 지원되지 않는 원자 변환을 구현할 수 있어요. 이 변경 전에는 공통 원자 타입을 증가시키고 이전 값을 읽으려면 compareAndSet() 함수가 있는 루프가 필요했어요.
공통 원자 타입의 모든 API처럼 이 함수들도 Experimental이에요. 옵트인하려면 @OptIn(ExperimentalAtomicApi::class) 어노테이션을 사용하세요.
다양한 종류의 업데이트를 수행하고 이전 또는 업데이트된 값을 반환하는 코드 예시를 볼게요.
import kotlin.concurrent.atomics.*
import kotlin.random.Random
@OptIn(ExperimentalAtomicApi::class)
fun main() {
val counter = AtomicLong(Random.nextLong())
val minSetBitsThreshold = 20
// Sets a new value without using the result
counter.update { if (it < 0xDECAF) 0xCACA0 else 0xC0FFEE }
// Retrieves the current value, then updates it
val previousValue = counter.fetchAndUpdate { 0x1CEDL.shl(Long.SIZE_BITS - it.countLeadingZeroBits()) or it }
// Updates the value, then retrieves the result
val current = counter.updateAndFetch {
if (it.countOneBits() < minSetBitsThreshold) it.shl(20) or 0x15BADL else it
}
val hexFormat = HexFormat {
upperCase = true
number {
removeLeadingZeros = true
}
}
println("Previous value: ${previousValue.toHexString(hexFormat)}")
println("Current value: ${current.toHexString(hexFormat)}")
println("Expected status flag set: ${current and 0xBAD != 0xBADL}")
}
이슈 트래커 YouTrack에 피드백을 남겨 주시면 감사하겠어요.
배열용 copyOf() 오버로드 지원
Kotlin 2.2.20은 copyOf() 함수의 실험적 오버로드를 도입했어요. 제네릭 타입 Array<T>와 모든 원시 배열 타입에 사용할 수 있어요.
이 함수로 배열을 더 크게 만들고 초기화 람다의 값을 사용해 새 요소를 채울 수 있어요. 이로써 커스텀 상용구 코드를 줄일 수 있고, 제네릭 Array<T> 크기를 조정하면 nullable 결과(Array<T?>)가 나오던 일반적인 불편함도 해결해요.
예시를 볼게요.
@OptIn(ExperimentalStdlibApi::class)
fun main() {
val row1: Array<String> = arrayOf("one", "two")
// Resizes the array and populates the new elements using the lambda
val row2: Array<String> = row1.copyOf(4) { "default" }
println(row2.contentToString())
// [one, two, default, default]
}
이 API는 Experimental이에요. 옵트인하려면 @OptIn(ExperimentalStdlibApi::class) 어노테이션을 사용하세요.
이슈 트래커에 피드백을 남겨 주시면 감사하겠어요.
Compose 컴파일러
이번 릴리스에서 Compose 컴파일러는 새 경고를 추가하고 빌드 메트릭 출력을 읽기 쉽게 개선해 품질을 향상시켰어요.
기본 파라미터에 대한 언어 버전 제한
이번 릴리스부터 Compose 컴파일러는 컴파일에 지정된 언어 버전이 abstract 또는 open composable 함수의 기본 파라미터를 지원하는 데 필요한 버전보다 낮으면 오류를 보고해요.
Compose 컴파일러에서 기본 파라미터는 abstract 함수의 경우 Kotlin 2.1.0부터, open 함수의 경우 Kotlin 2.2.0부터 지원돼요. 더 오래된 Kotlin 언어 버전을 타깃으로 하면서 더 새로운 버전의 Compose 컴파일러를 사용할 때, 라이브러리 개발자는 언어 버전이 지원하지 않더라도 abstract 또는 open 함수의 기본 파라미터가 공개 API에 여전히 나타날 수 있다는 점을 알아야 해요.
K2 컴파일러용 Composable 타깃 경고
이번 릴리스는 K2 컴파일러를 사용할 때 @ComposableTarget 불일치에 대한 경고를 추가해요.
예를 들면 이렇죠.
@Composable fun App() {
Box { // <-- `Box` is a `@UiComposable`
Path(...) // <-- `Path` is a `@VectorComposable`
^^^^^^^^^
warning: Calling a Vector composable function where a UI composable was expected
}
}
빌드 메트릭의 정규화된 이름
빌드 메트릭에 보고되는 클래스와 함수 이름은 이제 정규화되어, 서로 다른 패키지에서 같은 이름의 선언을 구분하기 쉬워졌어요.
추가로 빌드 메트릭은 이제 기본 파라미터의 복잡한 식 덤프를 포함하지 않아 읽기 쉬워졌어요.
Breaking changes와 deprecated
이 섹션은 주목할 만한 중요한 breaking changes와 deprecated를 강조해요.
- kapt 컴파일러 플러그인은 이제 기본으로 K2 컴파일러를 사용해요. 그 결과 플러그인이 K2 컴파일러를 사용할지 제어하는
kapt.use.k2프로퍼티가 deprecated됐어요. K2 컴파일러를 사용하지 않기 위해 이 프로퍼티를false로 설정하면 Gradle이 경고를 보여 줘요.
문서 업데이트
Kotlin 문서에 주목할 만한 변경이 몇 가지 있었어요.
-
Kotlin 로드맵 – 언어와 생태계 진화에 대한 Kotlin의 우선순위 업데이트 목록을 확인하세요.
-
프로퍼티 – Kotlin에서 프로퍼티를 사용할 수 있는 여러 방법을 배워요.
-
조건과 반복문 – Kotlin에서 조건과 반복문이 어떻게 동작하는지 배워요.
-
Kotlin/JavaScript – Kotlin/JS의 사용 사례를 살펴보세요.
-
웹 타깃팅 – Gradle이 웹 개발을 위해 제공하는 여러 타깃을 배워요.
-
Kotlin 데몬 – Kotlin 데몬과 빌드 시스템·Kotlin 컴파일러와 어떻게 함께 동작하는지 배워요.
-
코루틴 개요 페이지 – 코루틴 개념을 배우고 학습 여정을 시작하세요.
-
Kotlin/Native 바이너리 옵션 – Kotlin/Native의 바이너리 옵션과 구성 방법을 배워요.
-
Kotlin/Native 디버깅 – Kotlin/Native로 디버깅할 수 있는 여러 방법을 살펴보세요.
-
LLVM 백엔드 커스터마이징 팁 – Kotlin/Native가 LLVM을 사용하는 방식을 배우고 최적화 패스를 조정하세요.
-
Exposed의 DAO API 시작하기 – Exposed의 데이터 접근 객체(DAO) API를 사용해 관계형 데이터베이스에 데이터를 저장하고 검색하는 방법을 배워요.
-
R2DBC에 관한 Exposed 문서의 새 페이지:
-
HTMX 통합 – Ktor가 HTMX에 대한 실험적 일급 지원을 제공하는 방법을 배워요.
Kotlin 2.2.20으로 업데이트하는 방법
Kotlin 플러그인은 IntelliJ IDEA와 Android Studio에 번들 플러그인으로 배포돼요.
새 Kotlin 버전으로 업데이트하려면 빌드 스크립트에서 Kotlin 버전을 2.2.20으로 바꾸세요.
더 알아보기
이 문서는 Kotlin 공식 문서의 What's new in Kotlin 2.2.20 페이지를 바탕으로 작성됐어요. 더 자세한 내용과 최신 변경 사항은 공식 문서를 확인해 주세요.