Return is not explicitly disallowed in lambdas—it is allowed, it just does something different, which is sometimes (often!) what you want. The whole idea of nonlocal returns is to allow you to create constructions that work as if they were part of the language. You don’t expect an return
inside of an if
block to return only from the if
do you?
The idea of constructs like this is that you can create things like…
fun doSomethingIfTasksAreDone() {
tasks.forEach {
if( !it.done ) return // Returns from the function
}
// All tasks are done...
doSomething()
}
I suspect the problem is that you are coming from a language like Javascript and expecting Kotlin to behave the same way.
Respectfully, I’d like to suggest that if you have 100M DAU, you probably should be going through a testing and QA process before you deploy code into production. If you already have such a process, you might want to look into why something potentially this costly was able to get through it, and what could be done to improve the testing process (in the apps I work on, we have a critical test path that must pass even if we’re doing an emergency hotfix for, say, a security issue, to make sure that business-critical and revenue-critical parts of the app haven’t been affected by the change).
That said, of course, it’s nice to be able to catch as much as possible during development, before going to QA (and just in case QA misses it, before going to production). That’s where Kotlin and the IDE are nice but they are never a substitute for QA… it’s impossible for a language or IDE to prevent developers from making bugs (this is a mathematical fact; see Halting problem - Wikipedia). This is an annoying but unfortunate part of software development: As amazing as they are, tools can only take you so far.
There is a way to handle this better under the existing tools, though, so I won’t leave this without offering a suggestion 
My solution in cases like this is to use finally
to commit the transaction. I see the comment about not using finally
because you don’t want to commit the transaction if there’s an exception, but that’s easily remedied: Catch and rethrow any exceptions, and when doing so, set a flag somewhere to invalidate the transaction so it’s not committed on the way out via the finally
block.
For example (I haven’t tested this, but I use a similar approach):
suspend inline fun <T> withBatch(
f: (Transaction) -> T
): T {
var validTransaction = true
val transaction = NoSQL.createTransaction()
try {
val result = f(transaction)
return result
} catch (e: Throwable) {
validTransaction = false
throw e
} finally {
if(validTransaction) {
transaction.commit().await()
}
}
}
Optionally, you could also have a flag like returnedNormally
and set it to true after calling f(transaction)
, then check for it in the finally block and if it’s false, throw a fatal error to let the programmer know they returned the wrong way (if you want to disallow nonlocal returns). You could even have the fatal error be an assert so it only happens in debug builds…
(Please keep in mind the above code is untested)
I hope that was at least somewhat helpful (and I hope your business can recover from the loss).