Are there any common ways to handle "try-with-reso...
# getting-started
d
Are there any common ways to handle "try-with-resource" with many resources? Rather than having nesting
createA().use { createB().use { createC().use { ... } } }
e
if you're on JVM, Guava's Closer is close to what you requested
Copy code
Closer().use { closer ->
    val a = closer.register(createA())
    val b = closer.register(createB())
    val c = closer.register(createC())
    ...
d
I just wrote my own 🙂
Copy code
package com.stochastictinkr.kotlessing.util

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import java.util.LinkedList
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.coroutines.AbstractCoroutineContextElement
import kotlin.coroutines.CoroutineContext


class ResourceScope : AbstractCoroutineContextElement(Key), Disposable {
    companion object Key : CoroutineContext.Key<ResourceScope>

    private var mutex: Mutex = Mutex()
    private var closed = false
    private val resources = LinkedList<Disposable>()

    private suspend inline fun <R> withLock(block: () -> R): R = mutex.withLock(action = block)

    suspend fun <R : Disposable?> create(creator: suspend () -> R): R = withLock {
        check(!closed) { "Scope is already closed" }
        val resource = creator()
        if (resource != null) {
            resources.addFirst(resource)
        }
        resource
    }

    suspend fun remove(disposable: Disposable) = withLock {
        if (closed) false else resources.remove(disposable)
    }

    suspend infix fun <R : Disposable> R.moveTo(otherScope: ResourceScope) = withLock {
        if (closed) return false
        val resource = this
        otherScope.create {
            if (resources.remove(resource)) resource else null
        } == resource
    }

    override suspend fun dispose() {
        withLock {
            if (closed) return
            closed = true
        }
        resources.map { runCatching { it.dispose() } }
        resources.clear()
    }
}

interface Disposable {
    suspend fun dispose()
}

suspend inline fun <D : Disposable, R> D.use(block: (D) -> R): R =
    try {
        block(this)
    } finally {
        dispose()
    }

suspend inline fun <R : Disposable?> CoroutineScope.create(crossinline creator: suspend CoroutineScope.() -> R): R =
    requireNotNull(coroutineContext[ResourceScope]) { "No ResourceScope available" }.create { creator() }

suspend fun <R> CoroutineScope.resourcesScope(block: suspend CoroutineScope.() -> R): R {
    return ResourceScope().use { withContext(it, block) }
}