Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
1
Help us understand the problem. What are the problem?

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がなければ、設定してるかわからないのでエラーになる。
}
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
1
Help us understand the problem. What are the problem?