13
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

KotlinのContract機能について

Last updated at Posted at 2021-08-20
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がなければ、設定してるかわからないのでエラーになる。
}
13
2
0

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
  3. You can use dark theme
What you can do with signing up
13
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?