Kotlin 1.4-M1がリリースされたのでContractsの動向をチェックしてみた

## はじめに
先日、Kotlin 1.4のプレビューバージョンである[Kotlin 1.4-M1](https://blog.jetbrains.com/kotlin/2020/03/kotlin-1-4-m1-released/)のリリースがありました。Kotlin 1.4-M1では型推論、Contracts、コンパイラーなどの機能改善がありましたが、その中でもContractsのアップデート情報を紹介したいと思います。

## Kotlin Contractsとは
Kotlin 1.3からプログラムの正確性の向上を支援するためにExperimentalな機能としてContractsが導入されました。コンパイラに対して契約を宣言することで快適なコーディングの支援に繋がります。標準ライブラリでもいくつか利用されていて、例えばスコープ関数などでContractsが利用されています。

## Kotlin 1.4-M1のContractsの動向
Kotlin 1.4-M1では、Contractsの機能改善といくつかの標準ライブラリにConstractsが利用されていますので、それぞれ紹介したいと思います。まず初めに具体化型パラメータへの参照を許可されました。対象のIssueは[こちら](https://youtrack.jetbrains.com/issue/KT-28298)です。

```kotlin
@OptIn(ExperimentalContracts::class)
inline fun <reified T> assertIsInstance(value: Any?) {
    contract {
        returns() implies (value is T)
    }

    assertTrue(value is T)
}
```

上記の関数ではインスタンス型検証を行なっています。T型パラメータが具象化されているため、関数内でその型を確認することができ、Contractsを利用できるようになりました。上記の関数は今後、kotlin.testライブラリに追加されるようです。

次にfinalのメンバー関数にConstractsが利用可能となりました。対象のIssueは[こちら](https://youtrack.jetbrains.com/issue/KT-27582)です。また対象の[PR](https://github.com/JetBrains/kotlin/commit/f083edfac283f3487e40d056cbf7b660b4055f96#diff-69aa3d8d30bdfbc6877e0b7e38d7ec54R11)を見てみると、下記のようにfinalのメンバー関数でContractsが利用できることがわかります。今まではトップレベル関数でしかContractsを利用できなかったことを考えると、大きなサポートと言えます。

```kotlin:contractCallSites.1.4.kt 
open class Class {
    fun member(x: Boolean) {
        contract { returns() implies (x) }
    }

    inline fun inlineMember(x: Boolean) {
        contract { returns() implies (x) }
    }

    abstract fun abstractMember(x: Boolean) {
        <!CONTRACT_NOT_ALLOWED!>contract<!> { returns() implies (x) }
    }

    open fun openMemeber(x: Boolean) {
        <!CONTRACT_NOT_ALLOWED!>contract<!> { returns() implies (x) }
    }

    suspend fun suspendMember(x: Boolean) {
        contract { returns() implies (x) }
    }
}
```

次にmeasureTimeMillis()、measureNanoTime()の標準ライブラリにもConstractsが利用されていますので、それぞれ内容を確認してみましょう。対象のIssueは[こちら](https://youtrack.jetbrains.net/issue/KT-27856)です。

```kotlin:Timing.kt
public inline fun measureTimeMillis(block: () -> Unit): Long {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE) // Kotlin 1.4でContractsが利用された
    }
    val start = System.currentTimeMillis() |
    block()
    return System.currentTimeMillis() - start |
}

public inline fun measureNanoTime(block: () -> Unit): Long {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE) // Kotlin 1.4でContractsが利用された
    }
    val start = System.nanoTime()
    block()
    return System.nanoTime() - start
}
```

measureTimeMillis()、measureNanoTime()は特定のコードブロックの実行時間をベンチマークするための関数です。Contractsが利用されていることがわかります。次のmeasureTimeMillis()を呼び出すコードではコンパイルが通るようになります。

```kotlin
val x: Int
measureTimeMillis {
    x = 1 // コンパイルOK(Kotlin 1.4)
}

println(x) // コンパイルOK(Kotlin 1.4)
```

ですが、Contractsが利用されていないKotlin 1.3で実行してみるとコンパイルエラーになります。

```kotlin
val x: Int
measureTimeMillis {
    x = 1 // コンパイルNG(Kotlin 1.3)
}

println(x) // コンパイルN(Kotlin 1.3)
```

なぜダメなのでしょうか。その理由ですが、measureTimeMillis()のラムダ内では、再代入できない変数xが再割り当ての可能性があるため、初期化が禁止されているためです。後続のprintln(x)の部分では、変数xが初期化されていないと認識されます。

measureTimeMillis()内のcontractのラムダ内を見てみると、`callsInPlace(block, InvocationKind.EXACTLY_ONCE)`が定義されています。これは変数blockが一回だけ実行されることを定義しています。つまり、先ほどの変数xがmeasureTimeMillis()のラムダ内で一回しか実行されないため、変数xに1が初期化されて、後続処理である println(x)が1を出力する結果となります。

このようにしてmeasureTimeMillis()、measureNanoTime()にContractsが利用されました。では最後にuse()の標準ライブラリにもContractsが利用されているので見てみましょう。use()とはリソースの使用後にuse()を呼び出すことで、リソースの解放を行われてリソースの解放漏れを防ぐことができます。次のコードがuse()の実装内容で、対象のIssueは[こちら](https://youtrack.jetbrains.com/issue/KT-35216)です。

```kotlin:Closeable.kt
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R { 
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE) // Kotlin 1.4でContractsが利用された
    }   
    var exception: Throwable? = null
    try {
        return block(this)
    } catch (e: Throwable) {
        exception = e 
        throw e
    } finally {
        when {
            apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exception)
            this == null -> {}
            exception == null -> close()
            else ->
                try {
                    close()
                } catch (closeException: Throwable) {
                    // cause.addSuppressed(closeException) // ignored here
                }   
        }   
    }   
}
```

以下のuse()の呼び出しコードでは、再代入不可の変数xをuse()のラムダ内で初期化しています。今まではuse()のラムダ内で変数xの初期化ができなかったのですが、contract内で`callsInPlace(block, InvocationKind.EXACTLY_ONCE)`を定義しているため、ラムダ内の処理が一回だけと定義しています。その結果、変数xの初期化が正常に行われて、println(x)の結果として1が出力されます。

```kotlin
val x: Int
"".byteInputStream().use {
   x = 1 // コンパイルOK(Kotlin 1.4)
}

println(x) // コンパイルOK(Kotlin 1.4)
```

Kotlin 1.3ではuse()にContractsが利用されていないため、以下のようにコンパイルエラーになります。

```kotlin
val x: Int
"".byteInputStream().use {
   x = 1 // コンパイルNG(Kotlin 1.3)
}

println(x) // コンパイルNG(Kotlin 1.3)
```

## おわり
Kotlin 1.4 M1がリリースされましたので、Contractsのアップデート情報を紹介しました。ContractsはExperimentalな機能ですが、Kotlin 1.4のリリースがとても待ち遠しいですね。引き続き動向を追っていこうと思います。

## 参考
- https://blog.jetbrains.com/kotlin/2020/03/kotlin-1-4-m1-released/
- https://youtrack.jetbrains.com/issue/KT-27856
- https://youtrack.jetbrains.com/issue/KT-35216
- https://youtrack.jetbrains.com/issue/KT-28298
- https://youtrack.jetbrains.com/issue/KT-27582