Home > OS >  How to run a piece of code before continue/break/return statement in Kotlin elvis operator?
How to run a piece of code before continue/break/return statement in Kotlin elvis operator?

Time:08-26

Imagine a function like this:

fun foo(bar: Bar) {
    val bar2: Bar = bar.nullableThing ?: return
    // do something with bar2
}

This works perfectly until you want to run something (like showing a message) before that "return" statement. I think it would look like this:

fun foo(bar: Bar) {
    val bar2: Bar = bar.nullableThing ?: run { 
        println("Error!")
        return
    }
    // do something with bar2
}

but that does not work ('return' is not allowed here).

Edit: My issue was actually with the continue statement, I did not know this would work with return. Here is a new piece of code that is more similar to the actual code:

data class Item(val str: String?)

val items = listOf<Item>(Item("tag:a"), Item("tag:b"), Item(null), Item("tag:c"))

val taggedItems = mapOf<String, String>("a" to "ItemA", "b" to "ItemB")

fun main() {
    for (item in items) {
        val stringItem = item.str?.let {
            if (it.startsWith("tag:")) taggedItems[it.substringAfter("tag:")]
            else it
        } ?: run {
            println("Error: tag ${item.str} is invalid")
            continue
        }
        compute(stringItem)
    }
}

fun compute(itemStr: String) = println(itemStr)

https://pl.kotl.in/SSU_z-Ctu The error is 'break' or 'continue' jumps across a function or a class boundary.

Given this, I could create a function like this:

fun printError(item: Item): String? {
    println("Error: tag ${item.str} is invalid")
    return null
}

And then

... ?: printError(item) ?: continue

butt that would be awful

CodePudding user response:

You could just revert to traditional coding style:

fun main() {
    for (item in items) {
        val stringItem = item.str?.let {
            if (it.startsWith("tag:")) taggedItems[it.substringAfter("tag:")]
            else it
        }
        if (stringItem == null) {
            println("Error: tag ${item.str} is invalid")
            continue
        }
        // From here on, stringItem is smart-cast as non-nullable
        compute(stringItem)
    }
}

Though I would personally prefer avoiding anything that behaves similarly to a goto, such as continue:

fun main() {
    for (item in items) {
        val stringItem = item.str?.let {
            if (it.startsWith("tag:")) taggedItems[it.substringAfter("tag:")]
            else it
        }
        if (stringItem != null) {
            compute(stringItem)
        } else {
            println("Error: tag ${item.str} is invalid")
        }
    }
}

But if you really, really like to use scope functions as much as possible:

fun main() {
    for (item in items) {
        item.str?.let {
            if (it.startsWith("tag:")) taggedItems[it.substringAfter("tag:")]
            else it
        }.also {
            compute(it)
        } ?: println("Error: tag ${item.str} is invalid")
    }
}
  • Related