I agree with both of you, the + overload was bad and I’ve switched it out for a standard extension method, .autoClose()
. Due to the overhead of the ResourceManager, and the messiness of requiring the finally {}
block else all exceptions would be eaten (which is what people would inevitably do), I stand by my position that this is too important to just be a library function. If someone comes up with some cool new syntax for dealing with this that is natural, efficient, and safe, then I’ll be happy to use it. But so far no one has offered up anything which satisfies all three of those requirements.
The natural part is hard to satisfy. I would say that Kotlin property delegates were not natural to me when I first read about them, but now I use them like I never knew any better.
But I think the standard use
function satisfies efficiency and safety (otherwise it probably would not be part of the standard library). The only downside of use
is that it only works with Closeable
on the JVM target, but that is solvable: Generic use function
Assuming a generic version of use
, the remaining question is: Is it really that unnatural to nest a few use
s?
I am a method extraction fanatic, so when I am bothered by a deeply nested structure, I simply extract additional methods.
Why did you used this design with catch-finally things? I don’t really understand it. Here’s topic with my code: Is there standard way to use multiple resources? - #2 by yole I don’t really see any problems with exceptions. May be I missed something?
There’s also use
for AutoCloseable
, it is available in kotlin-stdlib-jre7
library: use - Kotlin Programming Language
In my opinion, yeah it’s pretty unnatural. Well, at the worst maybe just messy and ugly. Sure, those aren’t deal breakers, I’m not saying Kotlin is a bad language because of this, but for me it’s a major pain point. Everywhere else Kotlin is clean and accomplishes it’s goals with simplicity, but here it falls flat on it’s face. Even with additional methods you still have the issue that to get any kind of exception handling built in you always have to nest it inside another try-catch
.
To me this speaks of a poorly designed resource management system. As @alexd457 said, when a language uses exceptions to handle error states, resource management should be closely designed with it. Simply throwing the exception in the .use()
block and catching that in a subsequent try-catch
block underneath it is just dirty.
I’m not saying that it doesn’t work. .use()
certainly works, and you can use it perfectly safely. I am saying the current system is not very good and needs to be improved, and as far as I’ve seen extending the language itself is far and away the best option for doing that. I kind of understand the idea that accomplishing it with the stdlib is easier to work with and undersatnd, but I disagree. Complex stdlib methods are not easy to work with, language constructs built specifically around these goals are typically much better suited. .use()
is not a complex method by any means, but it’s simplicity brings it’s drawbacks.
Are there any plans for common (non JVM specific) AutoCloseable
and/or Closeable
interfaces, and use
extension function?
I’m writing mostly multiplatform libraries and I find it inconvenient to define my own use()
extensions for the lib users. I believe it’s nicer if they can simply use the stdlib functions (and I wouldn’t need to test it ) .
Why not defining some sort of deconstructor for each class?
Like the unusable (so meaningless) finalize() function in java.
Kolin could call this function 100% before beginning the object deallocation.
So a nice place for putting the expected close() into.
No it can’t. Deallocation is done by the JVM garbage collector and the control you have over that is quite low and depends on the jvm implementation. Maybe this can be done on native, but I don’t see this working on the JVM.
I know. Therefore I wrote
Kotlin native would have the potential.
Perhaps there is a possibility tweaking the compiler so that all objects are somehow soft-referenced by the “kotlin runtime” and then deconstructed and after that released (for java deallocation).
Just as the title says. Kotlin needs try-with-resources
or using
, or whatever.
Look at this simplified example in Java.
List<String> fileNames = Arrays.asList("file1", "mustBeEmpty", "file2");
for (String fileName : fileNames) {
OutputStream out = Files.newOutputStream(fileSystem.getPath(fileName));
try {
if (fileName.equals("mustBeEmpty"))
continue;
out.write(new byte[] {1, 2, 3});
} finally {
out.close();
}
}
Suppose I’d like to convert it to try-with-resources
(just as I should, because of a better syntax and exception suppression). Now it looks like this:
List<String> fileNames = Arrays.asList("file1", "mustBeEmpty", "file2");
for (String fileName : fileNames) {
try (OutputStream out = Files.newOutputStream(fileSystem.getPath(fileName))) {
if (fileName.equals("mustBeEmpty"))
continue;
out.write(new byte[]{1, 2, 3});
}
}
Nice, isn’t it? OK, let’s do the same in Kotlin… here’s the original code, converted to Kotlin by IDEA:
val fileNames = listOf("file1", "mustBeEmpty", "file2")
for (fileName in fileNames) {
val out = Files.newOutputStream(fileSystem.getPath(fileName))
try {
if (fileName == "mustBeEmpty")
continue
out.write(byteArrayOf(1, 2, 3))
} finally {
out.close()
}
}
So far so good, but I’d really like to use use
, no pun intended. So here’s the try-with-resources
version, again, converted by IDEA:
val fileNames = listOf("file1", "mustBeEmpty", "file2")
for (fileName in fileNames) {
Files.newOutputStream(fileSystem.getPath(fileName)).use { out ->
if (fileName == "mustBeEmpty")
continue
out.write(byteArrayOf(1, 2, 3))
}
}
Looks sort of nice too… except it doesn’t compile now! Because use
is not a regular statement, but instead is an inline function with a lambda expression as an actual parameter, and therefore continue
doesn’t work here. So IDEA failed to convert Java code properly, and we must do it by hand:
val fileNames = listOf("file1", "mustBeEmpty", "file2")
for (fileName in fileNames) {
Files.newOutputStream(fileSystem.getPath(fileName)).use { out ->
if (fileName == "mustBeEmpty")
return@use
out.write(byteArrayOf(1, 2, 3))
}
}
Now it suddenly doesn’t look nice at all. Worse, return@use
will get you a well-deserved warning from IDEA when there are multiple nested use
calls (not to mention those are ugly enough by themselves), so in order for it to be context-independent, we’ll have to do this:
val fileNames = listOf("file1", "mustBeEmpty", "file2")
for (fileName in fileNames) {
Files.newOutputStream(fileSystem.getPath(fileName)).use useFile@ { out ->
if (fileName == "mustBeEmpty")
return@useFile
out.write(byteArrayOf(1, 2, 3))
}
}
For real? I love Kotlin, and this is certainly not a game-breaking failure, but…
I admit, if we ever get to use non-local break
and continue
similar to non-local returns, it won’t be such an ugly thing anymore. But still, try-with-resources
and using
are excellent examples of good language design! I can understand that some language constructs can easily be replaced by function calls or other language constructs, but this is one example of a language construct that is very simple to use, always does its job and doesn’t really make the language overly complicated or anything like that.
Most of the time there is a better way of doing things than using continue or break with labels:
fileNames
.filterNot { it == "mustBeEmpty" }
.forEach { fileName ->
Files.newOutputStream(Paths.get(fileName)).use { outStream ->
outStream.write(byteArrayOf(1, 2, 3))
}
}
or with for each:
for (fileName in fileNames) {
if (fileName == "mustBeEmpty") {
continue
}
Files.newOutputStream(Paths.get(fileName)).use { outStream ->
outStream.write(byteArrayOf(1, 2, 3))
}
}
You can use this
for (fileName in fileNames) {
if (fileName != "mustBeEmpty") {
File(fileName).appendBytes(byteArrayOf(1, 2, 3))
}
}
Sometimes it is better this way. Sometimes not. If I decide it’s better to have a loop with breaks and continues rather than using functional-style chains. Kotlin is not a functional language after all.
And besides, all of the proposed “solutions” are wrong. All of them produce no file named “mustBeEmpty”, instead of producing an empty file with that name.
And of course, it should be noted that my example is obviously simplified. If you want something closer to real life, consider the program I’m working on lately. It’s a typical multi-threaded server. It has an accept()
loop that accepts new connections, performs some preliminary checks and if the checks pass, it spins up a new thread to serve that particular connection. Otherwise, the connection is closed immediately. There’s too much code, and it’s under NDA anyway, so instead of real code, consider this pseudocode:
while (!Thread.interrupted()) {
val connection = accept()
if (connection.ipAddress.isBanned) {
connection.close()
continue
}
executor.submit(ClientConnection(connection))
}
ClientConnection
wraps a low-level connection (which is a SocketChannel
) and implements Runnable
so that it can be handled by an ExecutorService
.
Now, at some point code got more and more complicated, closing a connection involved decrementing connection counter, writing a log message, and there were more cases when a connection should be closed, including not-so-obvious ones (such as when an exception is thrown) so I decided to introduce yet another connection wrapper: PendingConnection
. So I’d immediately wrap a connection in a PendingConnection
, and then it may be “promoted” into a ClientConnection
if it passes all the checks, or closed otherwise. PendingConnection
implements Closeable
, but its close()
method only actually closes it if it wasn’t promoted, otherwise it’s now ClientConnection
’s responsibility to close it when necessary. This allowed me to refactor it into something like
while (!Thread.interrupted()) {
PendingConnection(accept()).use { connection ->
if (connection.ipAddress.isBanned) {
continue
}
executor.submit(connection.promote())
}
}
This way, it doesn’t matter, whether a continue
or a break
is encountered, or an exception is thrown, the connection is guaranteed to be closed unless it was promoted and handed off to the executor. All the close()
-related cleanup code is now in a single place: the PendingConnection.close()
method, so there is no way I forget to decrement connection counter or anything like that. It’s a very good example of RAII-style refactoring… except that it doesn’t work in Kotlin because of the stupid lack of try-with-resources
!
I got bitten by this again. I have a loop inside use
that has code like this:
if (packet == null)
packet = queue.take()
connection.send(packet)
I get “Smart cast is impossible because ‘packet’ is a local variable that is captured by a changing closure”. If use
was a statement and not a function, there would be no such closure. That’s what you get for trying to get around a language limitation with a library function.
I get “Smart cast is impossible because ‘packet’ is a local variable that is captured by a changing closure”.
Changing local vars in a lambda could actually be possible for functions with inline lambdas (KT-7186) and especially for functions that declare calls-in-place contract (KT-28806) if those features were implemented.
KT-7186 has it’s target version set to 1.5.0. Is it save to assume that KT-28806 will therefor also be part of 1.5? They look to be pretty much the same thing.
I think it’s safe to assume that KT-28806 will be implemented not later than KT-7186.
It’s possible will all sorts of lambdas. Other languages can do it, so why not Kotlin? I mean, I can see that by allowing it, the compiler would have to change the local variables to be stored on the heap instead, but that’s an implementation detail.
I find it a bit uncomfortable that declaring a function as inline changes the semantics of the language itself.
If use has a contract with CALLS_IN_PLACE, then I think the continue should work
You mean the experimental contract? Yeah, right. First, create a problem. Then, create an experimental tool to solve it. Ages later, that tool makes it to the stable production phase. Maybe.
The main problem is that there is no reason not to have it. There is a reason not to have checked exceptions. Or primitive types. Or mandatory manual memory management. But this thing caused no problems whatsoever in other languages, so why Kotlin refuses it?