search
LoginSignup
1

More than 1 year has passed since last update.

posted at

updated at

Organization

KotlinのContract機能について

KotlinのContract機能について

by ko2ic
1 / 14

概要

Kotlin Contractsとは簡単に言うと、コンパイラに対象のメソッドを呼んだあとは、「想定のエラーは起きませんよ」と伝えることができる機能です。Kotlin1.3から導入されました。まだ、experimentalな機能です。


簡単な例


data class DataDto(val str: String)

private fun process1(dto: DataDto?) {
    validate(dto)
    println(dto.str) // コンパイルエラー
}

private fun validate(dto : DataDto?) {
    if (dto == null) {
        throw IllegalArgumentException()
    }
}

この例では、dtoはnullableですが、validate() メソッドの後では、nullはありえません。
しかし、コンパイラはそれがわからないので dto.str をエラーにします。
これがエラーの内容です。
Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type DataDto?


これをContractsによって、コンパイラに伝えると


+ @ExperimentalContracts
  private fun validate(dto : DataDto?) {
+    contract {
+        returns() implies (dto != null)
+    }
     if (dto == null) {
         throw IllegalArgumentException()
     }

これだけで、validate()メソッドを呼んだ後のdtoはnullはあり得ないことが、コンパイラに伝わります。
そのため dto.strはコンパイルが通ります。


制限事項


引数だけ対象になっています。
たとえば、以下のようにDataDto#str がnullでない場合を保証しようとしてもできません。

data class DataDto(val str: String?)

@ExperimentalContracts
private fun process1(dto: DataDto?) {
    validate(dto)
    println(dto.str) // コンパイルエラー
}

@ExperimentalContracts
private fun validate(dto : DataDto?) {
   contract {
       returns() implies (dto?.str != null) // コンパイルエラー
   }
     if (dto?.str == null) {
         throw IllegalArgumentException()
     }

条件指定


returns


returns で返した値の場合だけコンパイルエラーを回避できるようできます。
以下の例は、returns(true) でtrueの場合だけ、コンパイルが通るようになります。

@ExperimentalContracts
private fun process2(dto: Any) {
    if (isDataDto(dto)){
        println(dto.str) // コンパイルエラーなし
    }
}

@ExperimentalContracts
fun isDataDto(dto: Any): Boolean {
    contract {
        returns(true) implies (dto is DataDto)
    }
    return dto is DataDto
}

isDataDto() がtrueかどうかわからない場合は、当然コンパイルエラーになります。

@ExperimentalContracts
private fun process2(dto: Any) {
    if (isDataDto(dto)){
        println(dto.str) // コンパイルエラーなし
    }
+   println(dto.str) // コンパイルエラー
}

returnsNotNull

正直、よくわかってないです。わかる方教えて欲しいです。

メソッドの戻り値で、nullがこないってこと?
だとしたら、メソッド定義でnot nullにすればいいだけな気がしています。


callsInPlace


関数が呼ばれる回数を保証して、コンパイルエラーを回避させます。
AT_MOST_ONCE=0 or 1 、AT_LEAST_ONCE=1以上、EXACTLY_ONCE=1、UNKNOWN=何回でも、を指定できます。

@ExperimentalContracts
inline fun <R> run(block: () -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

@ExperimentalContracts
fun process3() {
    val i: Int
    run {
        i = 1 // callsInPlaceがなければ、valに二度設定できるかもしれないのでエラーになる。
    }
    println(i) // callsInPlaceがなければ、設定してるかわからないのでエラーになる。
}

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
1