Hi,
This might be too late to change, but I’m puzzled by the decision for use
function to allow a null receiver:
public inline fun <T : AutoCloseable?, R> T.use(block: (T) -> R): R {
var exception: Throwable? = null
try {
return block(this)
} catch (e: Throwable) {
exception = e
throw e
} finally {
this.closeFinally(exception)
}
}
-
this
in this context is usually a resource, and null
cannot possibly be a resource
- In fact,
null
indicates a lack of resource. For example, we have a function getLock
which returns null
if the lock cannot be obtained. In this case, Kotlin’s use
will still execute the block, which is a potentially disastrous bug.
What I almost always want is:
t?.use { ... }
i.e. if it is null, don’t execute the block.
However, writing in this way is error-prone, as ? will easily be forgotten.
So we ended up implementing our own use1
which does not allow null
, and banning use
from our codebase.
Any thoughts?
While I can’t think of any reason why I would want to call null.use { ... }
I don’t think it is more error prone.
If you call use
on a nullable type you just have to handle null
inside of the use-block, which leads to you noticing that you did not really want to use it that way.
If I had implemented use I would not have made the receiver nullable but I don’t really mind.
The following is assuming that making use function available on nullable types is preferable.
Think about how it would work if use were not an API function, but rather a language construct. It would work in the same way: you would need to handle the nullability of the receiver within the use block. Otherwise it would be less intuitive and inconsistent with other language constructs (if, while, etc).
The function is probably designed in this way to be most similar to a language construct.
See KT-12894 for the reason why we allowed nullable receivers in use
.
I checked the KT-12894 and I think it could be argued both ways, depending on the case.
In our case, the pattern is:
val lock = getLock()
lock.use {
// the block which must not execute if we didn't get the lock
}
Within the block there is no use of it
, which is just a simple lock, so the compiler won’t complain (in reply to @Wasabi375’s point).
I can see how both pro and con are specific to the use case. Another specificity of our case is that not obtaining the lock is not an exception, and if we don’t obtain it we simply do not want to execute the critical block.
We actually want:
lock?.use {
// OR
lock!!.use {
but this is easily missed. Ideally I expected the compiler to complain about lock.use {
if lock
is nullable.
I think, you should not blame compiler or language if something is missing in your program logic.