본문 바로가기

트러블슈팅

[Kotlin] Contracts

코틀린 가이드의 번역내용입니다.

https://kotlinlang.org/docs/reference/whatsnew13.html

 

What's New in Kotlin 1.3 - Kotlin Programming Language

 

kotlinlang.org

 

Contracts

Kotlin 컴파일러는 광범위한 정적 분석을 수행하여 경고를 제공하고 boilerplate code를 줄입니다. 가장 주목할만한 기능 중 하나는 스마트 캐스트입니다. 컴파일러의 type check 에 따라 자동으로 type 캐스트를 수행 할 수 있습니다.

fun foo(s: String?) {
    if (s != null) s.length // Compiler automatically casts 's' to 'String'
}

 

 

하지만 type check 로직이 별도의 함수로 분리되면 smartcast 는 사라집니다.

fun String?.isNotNull(): Boolean = this != null // type check 로직 분리됨

fun foo(s: String?) {
    if (s.isNotNull()) s.length // No smartcast :(
}

 

To improve the behavior in such cases, Kotlin 1.3 introduces experimental mechanism called contracts.

Contracts allow a function to explicitly describe its behavior in a way which is understood by the compiler. Currently, two wide classes of cases are supported:

Improving smartcasts analysis by declaring the relation between a function's call outcome and the passed arguments values:

 

이러한 동작을 개선하기 위해 Kotlin 1.3에는 Contracts 라는 experimental mechanism 이 도입되었습니다.
Contracts 는 함수가 컴파일러가 이해하는 방식으로 동작을 명시적으로 설명 할 수 있도록 합니다.

현재 두 가지 종류의 사례가 지원됩니다.

  • 함수의 호출 결과와 전달 된 인수 값 사이의 관계를 선언하여 스마트 캐스트 분석을 개선합니다.
@ExperimentalContracts
fun requireWithContract(condition: Boolean) {
    // 이 함수가 성공적으로 리턴되면 컴파일러에게 condition (함수)는 true 라는 것을 알려준다.
    contract { returns() implies condition }

    // condition 이 false 이면 exception 발생하므로 함수가 성공적으로 리턴되지 않는다.
    if (!condition) throw IllegalArgumentException()
}

fun String?.requireWithFunction() = this != null

@ExperimentalContracts
fun foo(s: String?) {
    if (s.requireWithFunction()) {
        s.replace(" ", "") // String? 에 대한 null check error 발생, ? 또는 !! 필요
        print(s)
    }

    requireWithContract(s is String)
    s.replace(" ", "") //smart cast 되어 ? 필요없음
}

 

  • kotlin-stdlib 에 적용된 예시
/**
 * Returns `true` if this nullable collection is either null or empty.
 * @sample samples.collections.Collections.Collections.collectionIsNullOrEmpty
 */
@SinceKotlin("1.3")
@kotlin.internal.InlineOnly
public inline fun <T> Collection<T>?.isNullOrEmpty(): Boolean {
	// 이 함수가 false 를 반환하는 경우, this@isNullOrEmpty != null 임을 컴파일러에게 알려준다.
    contract {
        returns(false) implies (this@isNullOrEmpty != null)
    }

    return this == null || this.isEmpty()
}

fun abc() {
    val abc: List<Int>? = null

    abc.indexOf(0) // null type check 에러

    if (abc.isNullOrEmpty()) {
        abc.indexOf(0) // null type check 에러
    }else{
        abc.indexOf(0) // smart cast
    }
}

 

  • 고차 함수가있을 때 변수 초기화 분석 개선 (아직 적용 안해봄)
fun synchronize(lock: Any?, block: () -> Unit) {
    // It tells compiler:
    // "This function will invoke 'block' here and now, and exactly one time"
    contract { callsInPlace(block, EXACTLY_ONCE) }
}

fun foo() {
    val x: Int
    synchronize(lock) {
        x = 42 // Compiler knows that lambda passed to 'synchronize' is called
               // exactly once, so no reassignment is reported
    }
    println(x) // Compiler knows that lambda will be definitely called, performing
               // initialization, so 'x' is considered to be initialized here
}