KotlinのCloseableのuseの挙動が気になる

# はじめに

今更ながら、Kotlinの学習を始めました。
長らくJavaメインでやってましたが、Kotlinだと同じコードがスッキリ書けて、とても気持ち良いですね！

今回は、そんな気持ち良いポイントの一つ`use`の挙動が気になったので、試してみました。


# useって？

`Closeableなオブジェクト.use`と書くことで、使い終わったら自動的にクローズしてくれます。

例えば、こんな感じで使用します。

```kotlin
// ファイルを取得する
val file = File("〜")

// ファイルに出力する
FileWriter(file).use { 
    it.write("文字列")
}
```


# 気になったポイント

Kotlinの気持ち良くないポイントの「非ローカルリターン」でも、ちゃんとクローズしてくれるのか？

例えば、初心者のハマりポイントのこんなコード...

```kotlin
fun method(list: List<String>) {
    val notEmptyList: MutableList<String> = mutableListOf()
    list.forEach {
        if (it.isEmpty()) {
            return // list.forEachではなく、methodからリターンしてしまう
        } else {
            notEmptyList += "$it is not empty"
        }
    }
    // notEmptyListを利用した処理
}
```

このように、Kotlinは`return`が特徴的で、`return@関数名`といった感じでリターンする関数を指定することもできます。

これを`use`と併用しても、ちゃんとクローズしてくれるのか試してみました。


# 検証

## Closeableなオブジェクト

以下の通り、適当なCloseableオブジェクトを定義します。

```kotlin
class TestCloseable(private val name: String) : Closeable {

    init {
        println("$name: init")
    }

    fun success() {
    }

    fun error() {
        throw Exception()
    }

    override fun close() {
        println("$name: close")
    }

}
```

## 検証ケース

`TestCloseable`を生成して使うだけ。

```kotlin
fun test1() {
    TestCloseable("Test 1").use {
        it.success()
    }
}
```

同じく`TestCloseable`を生成して使うだけだが、例外が発生する。

```kotlin
fun test2() {
    TestCloseable("Test 2").use {
        it.error()
    }
}
```

ラムダ式内で`TestCloseable`を生成して、上位の関数をリターンする。

```kotlin
fun test3() {
    listOf(1, 2, 3).forEach { i: Int ->
        TestCloseable("Test 3-$i").use { testCloseable: TestCloseable ->
            if (i >= 2) return@forEach
            testCloseable.success()
        }
    }
}
```

ラムダ式内で`TestCloseable`を生成して、メソッド自体からリターンする。

```kotlin
fun test4() {
    listOf(1, 2, 3).forEach { i: Int ->
        TestCloseable("Test 4-$i").use { testCloseable: TestCloseable ->
            if (i >= 2) return@test4
            testCloseable.success()
        }
    }
}
```

## 検証結果

以下の通り、いずれのケースでもきちんとクローズしてくれている。
クローズ漏れのリスクなく、安全に使えることがわかった。

**test1**

```
Test 1: init
Test 1: close
```

**test2**

```
Test 2: init
Test 2: close
```

**test3**

```
Test 3-1: init
Test 3-1: close
Test 3-2: init
Test 3-2: close
Test 3-3: init
Test 3-3: close
```

**test4**

```
Test 4-1: init
Test 4-1: close
Test 4-2: init
Test 4-2: close
```


# 実装を覗いてみる

そもそも`use`の実装がどうなっているのか、見てみた。

```kotlin
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    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
                }
        }
    }
}

internal fun Closeable?.closeFinally(cause: Throwable?) = when {
    this == null -> {}
    cause == null -> close()
    else ->
        try {
            close()
        } catch (closeException: Throwable) {
            cause.addSuppressed(closeException)
        }
}
```

ポイントは以下の通り。

1. `Closeable`を継承したオブジェクトをラムダ式`block`の引数にして、実行する
1. 例外を捕捉した場合、退避しておき呼び出し元に伝播する
1. `finally`処理で、
    - 自インスタンス（`Closeable`継承オブジェクト）が`null`なら何もしない
    - 異常終了（退避した例外がある）なら、抑制例外に追加して`close`
    - 正常終了（退避した例外がない）なら、単純に`close`


# 提案

複数のCloseableオブジェクトを使用して、同じことをしたい場合がある。
元の実装をまねて、以下のような関数を定義すると良いかもしれない。

```kotlin
inline fun <T1 : Closeable?, T2 : Closeable?, R> Pair<T1, T2>.use(block: (T1, T2) -> R): R {
    var exception: Throwable? = null
    try {
        return block(this.first, this.second)
    } catch (e: Throwable) {
        exception = e
        throw e
    } finally {
        if (this.second != null) this.second.closeFinally(exception)
        if (this.first != null) this.first.closeFinally(exception)
    }
}

fun Closeable?.closeFinally(cause: Throwable?) {
    when {
        this == null -> {}
        cause == null -> close()
        else ->
            try {
                close()
            } catch (closeException: Throwable) {
                cause.addSuppressed(closeException)
            }
    }
}
```

こんな感じで利用する。

```kotlin
fun test5() {
    val a = TestCloseable("Test 5-a")
    val b = TestCloseable("Test 5-b")
    Pair(a, b).use { a: TestCloseable, b: TestCloseable ->
        a.success()
        b.success()
    }
}
```

上記は`Pair`を利用してるが、`Triple`を利用すれば3つのCloseableオブジェクトで対応できる。


# 参考

https://github.com/shion-skgw/KotlinCloseableTest