Android
Kotlin
Swift

Kotlinで使っていいスコープ関数は「apply」「also」のみ


SwiftとKotlinの比較

Swiftで、ある変数がnullかどうかで処理を分岐したいときは

if let xxx = xxx {

xxxがnullではない時
} else {
xxxがnullの時
}

この方法を使用することが多いと思います。

これはスマートで安全で問題ないです。

上記Swiftと同じようなことをKotlinで行おうとすると大抵の人は↓のようなコードを書くと思います。

xxx?.let {

xxxnullではない時
} ?: run {
xxxnullの時
}

ですがこのコードはかなり危険なコードとなり、

バグの温床となります。


Kotlinで思惑通り動く例

fun main(args: Array<String>) {

// リスナー nullで未指定
var listener: ((message: String) -> Unit)? = {
println(it)
}

// 表示させるメッセージ
var message: String? = "Hey!"

message?.let {
listener?.invoke(it)
} ?: run {
listener?.invoke("message is null!")
}
}

この処理結果はhey!と出力されます。

このコードを書いた人の思惑通りの正しい処理です。


Kotlinで予期しない動きとなる例

しかし、もしもリスナーがnullだった場合

このコードは本来望んだ動きと違う動きする可能性があります。(バグの温床)

fun main(args: Array<String>) {

// リスナー nullで未指定
var listener: ((message: String) -> Unit)? = {
println(it)
}

// 表示させるメッセージ
var message: String? = "Hey!"

// リスナーをnull(未指定)にしてみる
listener = null

message?.let {
listener?.invoke(it)
} ?: run {
println("message is null!")
}
}

listenernullにしてみました。

ですが、messageはnullではないので先程と同じくhey!と出力される状況になり、今回はリスナーを登録してないので何も表示されないと思いきや…

この実行結果はmessage is null!となります。

これは、

letrunなどのスコープ関数はそのブロック内の最後の処理結果を返すためです。

なのでlistener?.invoke(it)の処理でlistenernullのためletの戻り値はnullとなり、

後に続く処理の?:で引っかかりrunの中身まで実行されてしまうからです。

詳しくは↓

Kotlin スコープ関数 用途まとめ

https://qiita.com/ngsw_taro/items/d29e3080d9fc8a38691e


結論

結論から言いますと、

Kotlinのスコープ関数でletrunは使わないほうが無難です。

殆どの場合、スコープ関数で値を返したい状況はないものです。

let、runの特性は理解していたので気をつければ大丈夫と思っていたのですが

今回listener?のようにチェーンでつなげたので気づきにくく見落としてました。。。

使わないほうが無難!

letalso

runapplyに置き換えて使用することをおすすめします。