Language support could help a lot.
If there were language support, there needs to be a way to handle exceptions easily in the constructor without interfering with how fields are declared.
I try to deal with ownership from a library, but I still have issues working with the language. For example:
trait Disposer {
def disposes[A: Disposable](a: A): A = ???
def close(): Unit = ???
}
class Composite extends Disposer {
val resource1 = this.disposes(allocate1())
val resource2 = this.disposes(allocate2())
}
Above, Disposer
maintains state that stores every resource with ownership declared with the disposes
method. Disposer.close
then disposes the resources in reverse order.
But this is not enough. What happens when the constructor throws an exception?
One of the more vexing issues with the way constructors work in Scala is the fact that the syntax for declaring local variables and fields are the same:
class Composite extends Disposer {
val resource1 = this.disposes(allocate1()) // this is a field
val resource2 = this.disposes(allocate2()) // this is a field
}
class Composite extends Disposer {
try {
val resource1 = this.disposes(allocate1()) // this is NOT a field
val resource2 = this.disposes(allocate2()) // this is NOT a field
} catch {
case t: Throwable => // Exceptions thrown by constructor
this.close()
throw t
}
}
I currently deal with this in the library in a less than elegant way:
def Construct[A <: Disposer](f: Disposer => A): A = {
val disposer = Disposer()
try {
val resource = f(disposer)
// Transfer ownership from the disposer to the object
resource.disposes(disposer.release())
resource
} finally {
disposer.dispose()
}
}
class Compose(resource1: Resource, resource2: Resource) extends Disposer
object Composite {
def apply(): Composite = {
Construct { disposer =>
val resource1 = this.disposes(allocate1())
val resource2 = this.disposes(allocate2())
Composite(resource1, resource1)
}
}
}
The Construct
syntax transfers ownership from the disposer to the resource, but only if no exceptions are thrown. If an exception is thrown, the disposer frees all resources. What I’m essentially doing here is doing all of my construction outside the constructor and only after all the construction is successful, transfer ownership to the Composite
object via the constructor to avoid throwing from the constructor at all.
This is where the language can help. If I could intercept any exceptions thrown from the constructor(s), I could delegate to close
and ensure I don’t leak resources in this case.
For example:
trait Disposer {
def disposes[A: Disposable](a: A): A = ???
def close(): Unit = ???
}
class Composite extends Disposer {
val resource1 = this.disposes(allocate1())
val resource2 = this.disposes(allocate2())
} catch {
case t: Throwable => // Exceptions thrown by constructor
this.close()
throw t
}