Is there a tidy way to do chained initialisation logic in Kotlin?

46 views Asked by At

After opening a file, someone has to close it.

Kotlin has the convenient use { ... } method for the case where you're doing all the work with the file in-place.

But sometimes, you have to do some other things with the file and return another object which gains ownership.

Currently, I am doing something like this:

    fun open(path: Path, fileMode: FileMode): StoreFile {
        var success = false
        val channel = FileChannelIO.open(path, fileMode)
        try {
            if (channel.size == 0 && fileMode == FileMode.READ_WRITE) {
                writeInitialContent(channel)
            }

            val result = StoreFile(channel)
            success = true
            return result
        } finally {
            if (!success) {
                channel.close()
            }
        }
    }

writeInitialContent could fail, in which case I would have to close the file. If it doesn't fail, then StoreFile takes ownership and becomes the thing which the caller has to close.

This is a common kind of nastiness in Java, and the way I've had to write it in Kotlin isn't really any cleaner than Java, and with the way it's currently written, there's still the potential for double-closing if StoreFile itself also happens to close the file on failure.

You hit the same basic problem even trying to buffer a stream, which is also fairly common. In this case, I don't know whether buffered() will throw, and if it does, nobody closes the file. In this case, buffered() itself could deal with the ugliness, but I checked its code and it does not.

    fun readSomething(path: Path): Something {
        path.inputStream().buffered().use { stream ->
            // ...
        }
    }

Is there a better way to structure this?

0

There are 0 answers