I think the current require function with the lazyMessage allows unintentional usage.
This is it:
@kotlin.internal.InlineOnly
public inline fun require(value: Boolean, lazyMessage: () -> Any): Unit {
contract {
returns() implies value
}
if (!value) {
val message = lazyMessage()
throw IllegalArgumentException(message.toString())
}
}
lazyMessage is of type () -> Any. It is intended to compute an error message, which is then used to build the IllegalArgumentException. Therefore, I believe it should be of type () -> String.
The motivation for this post is something I saw in code review today:
require(...) {
throw SomeOtherException()
}
The snippet above compiles and “works”. However, it is not very nice: The exception is not thrown when the value in require is checked, but during the message construction val message = lazyMessage(), when lazyMessage() is called.
This would still work even if you change the return type to String. throw is of type Nothing, which is a subtype of every type. See for yourself!
public inline fun require2(value: Boolean, lazyMessage: () -> String): Unit {
if (!value) {
val message = lazyMessage()
throw IllegalArgumentException(message)
}
}
fun main() {
val condition = false
require(condition) { throw IllegalStateException() }
}
Why is this a problem? Whether producing a message or having some other effect, the block is clearly executed only when the requirement fails. So wouldn’t you expect the exception to be thrown at that point?
Since every Kotlin (and Java) object has a toString() method, it’s quite common to pass an object and then for the recipient to call toString() as needed — even implicitly, as that’s what happens when you use objects in string templates or use + to concatenate them.