Automatic resource management with try-with-resources semantics through the Using utility. Provides safe resource handling that ensures proper cleanup even in the presence of exceptions.
Manage a single resource with automatic cleanup through the Releasable typeclass.
object Using {
/**
* Use a resource in a Try context with automatic cleanup
* @param resource Resource to manage (call-by-name for lazy evaluation)
* @param f Operation to perform on the resource
* @param releasable Typeclass instance for resource cleanup
* @return Try containing the result or any exception
*/
def apply[R: Releasable, A](resource: => R)(f: R => A): Try[A]
/**
* Use a resource with automatic cleanup, throwing exceptions directly
* @param resource Resource to manage
* @param body Operation to perform on the resource
* @param releasable Typeclass instance for resource cleanup
* @return Result of the operation
* @throws Exception if resource creation or operation fails
*/
def resource[R, A](resource: R)(body: R => A)(implicit releasable: Releasable[R]): A
}Usage Examples:
import scala.util.Using
import java.io.{FileInputStream, BufferedReader, FileReader}
// Using with Try (exception handling)
val content = Using(new FileInputStream("data.txt")) { fis =>
val bytes = new Array[Byte](fis.available())
fis.read(bytes)
new String(bytes)
}
content match {
case Success(data) => println(s"File content: $data")
case Failure(exception) => println(s"Error reading file: $exception")
}
// Using with direct exceptions
try {
val result = Using.resource(new BufferedReader(new FileReader("config.txt"))) { reader =>
reader.lines().toArray.mkString("\n")
}
println(result)
} catch {
case ex: Exception => println(s"Failed to read config: $ex")
}
// Combining with other operations
val processedData = Using(new FileInputStream("input.dat")) { inputStream =>
val data = readBinaryData(inputStream)
processData(data)
transformData(data)
}.getOrElse(defaultData)Manage multiple resources simultaneously with guaranteed cleanup of all resources.
/**
* Manage two resources with automatic cleanup
* @param resource1 First resource to manage
* @param resource2 Second resource to manage (call-by-name)
* @param body Operation to perform on both resources
* @return Result of the operation
* @throws Exception if any resource creation or operation fails
*/
def resources[R1: Releasable, R2: Releasable, A](
resource1: R1,
resource2: => R2
)(body: (R1, R2) => A): A
/**
* Manage three resources with automatic cleanup
* @param resource1 First resource to manage
* @param resource2 Second resource to manage (call-by-name)
* @param resource3 Third resource to manage (call-by-name)
* @param body Operation to perform on all resources
* @return Result of the operation
*/
def resources[R1: Releasable, R2: Releasable, R3: Releasable, A](
resource1: R1,
resource2: => R2,
resource3: => R3
)(body: (R1, R2, R3) => A): A
/**
* Manage four resources with automatic cleanup
* @param resource1 First resource to manage
* @param resource2 Second resource to manage (call-by-name)
* @param resource3 Third resource to manage (call-by-name)
* @param resource4 Fourth resource to manage (call-by-name)
* @param body Operation to perform on all resources
* @return Result of the operation
*/
def resources[R1: Releasable, R2: Releasable, R3: Releasable, R4: Releasable, A](
resource1: R1,
resource2: => R2,
resource3: => R3,
resource4: => R4
)(body: (R1, R2, R3, R4) => A): AUsage Examples:
import scala.util.Using
import java.io.{FileInputStream, FileOutputStream, BufferedReader, FileReader}
// Copy file with automatic cleanup of both streams
Using.resources(
new FileInputStream("source.txt"),
new FileOutputStream("destination.txt")
) { (input, output) =>
val buffer = new Array[Byte](8192)
var bytesRead = 0
while ({ bytesRead = input.read(buffer); bytesRead != -1 }) {
output.write(buffer, 0, bytesRead)
}
}
// Process multiple files
Using.resources(
new BufferedReader(new FileReader("config.properties")),
new BufferedReader(new FileReader("data.csv")),
new FileOutputStream("report.txt")
) { (configReader, dataReader, reportOutput) =>
val config = loadProperties(configReader)
val data = parseCSV(dataReader)
val report = generateReport(config, data)
reportOutput.write(report.getBytes)
}
// Database operations with multiple connections
Using.resources(
createReadConnection(),
createWriteConnection()
) { (readConn, writeConn) =>
val data = fetchData(readConn)
val processed = processData(data)
saveResults(writeConn, processed)
}Advanced resource management with a manager that can handle an arbitrary number of resources.
final class Manager {
/**
* Register and return a resource for management
* @param resource Resource to manage
* @param releasable Typeclass instance for resource cleanup
* @return The resource itself
*/
def apply[R: Releasable](resource: R): R
/**
* Register a resource for management without returning it
* @param resource Resource to manage
* @param releasable Typeclass instance for resource cleanup
*/
def acquire[R: Releasable](resource: R): Unit
}
object Manager {
/**
* Execute operation with resource manager
* @param op Operation that uses the manager to acquire resources
* @return Try containing the result or any exception
*/
def apply[A](op: Manager => A): Try[A]
}Usage Examples:
import scala.util.Using
// Managing multiple resources dynamically
val result = Using.Manager { manager =>
val file1 = manager(new FileInputStream("file1.txt"))
val file2 = manager(new FileInputStream("file2.txt"))
val output = manager(new FileOutputStream("merged.txt"))
// All resources are automatically managed
mergeFiles(file1, file2, output)
}
// Conditional resource acquisition
Using.Manager { manager =>
val primaryDB = manager(connectToPrimaryDatabase())
val backupDB = if (primaryDB.isHealthy) {
None
} else {
Some(manager(connectToBackupDatabase()))
}
performDatabaseOperations(primaryDB, backupDB)
}
// Resource acquisition in loops
Using.Manager { manager =>
val outputFiles = (1 to 10).map { i =>
manager(new FileOutputStream(s"output_$i.txt"))
}
// Process data and write to all files
processDataToMultipleFiles(inputData, outputFiles)
}The Releasable typeclass defines how resources should be cleaned up.
trait Releasable[-R] {
/**
* Release the resource, cleaning up any held resources
* @param resource Resource to release
*/
def release(resource: R): Unit
}
object Releasable {
/**
* Implicit instance for AutoCloseable resources
*/
implicit object AutoCloseableIsReleasable extends Releasable[AutoCloseable] {
def release(resource: AutoCloseable): Unit = resource.close()
}
/**
* Implicit instance for scala.io.Source (Scala 2.11 only)
*/
implicit object SourceReleasable extends Releasable[Source] {
def release(resource: Source): Unit = resource.close()
}
}Custom Releasable Instances:
import scala.util.Using.Releasable
// Custom resource type
case class DatabaseConnection(url: String) {
def close(): Unit = {
// Close database connection
println(s"Closing connection to $url")
}
}
// Custom Releasable instance
implicit val dbReleasable: Releasable[DatabaseConnection] = new Releasable[DatabaseConnection] {
def release(resource: DatabaseConnection): Unit = resource.close()
}
// Usage with custom resource
Using(DatabaseConnection("jdbc:mysql://localhost/db")) { conn =>
// Perform database operations
executeQuery(conn, "SELECT * FROM users")
}
// Thread pool management
case class ThreadPool(size: Int) {
def shutdown(): Unit = {
println(s"Shutting down thread pool of size $size")
}
}
implicit val threadPoolReleasable: Releasable[ThreadPool] =
(resource: ThreadPool) => resource.shutdown()
Using(ThreadPool(10)) { pool =>
// Execute tasks using thread pool
executeTasks(pool, tasks)
}Graceful Degradation:
import scala.util.Using
def readConfigWithFallback(primaryPath: String, fallbackPath: String): Config = {
Using(new FileInputStream(primaryPath)) { input =>
parseConfig(input)
}.recoverWith { case _ =>
Using(new FileInputStream(fallbackPath)) { input =>
parseConfig(input)
}
}.getOrElse(defaultConfig)
}Resource Validation:
import scala.util.Using
def validateAndProcessFile(path: String): Either[String, ProcessedData] = {
Using(new FileInputStream(path)) { input =>
if (input.available() == 0) {
Left("File is empty")
} else {
val data = readData(input)
if (isValidData(data)) {
Right(processData(data))
} else {
Left("Invalid data format")
}
}
}.fold(
exception => Left(s"Error reading file: ${exception.getMessage}"),
identity
)
}Retry with Resources:
import scala.util.Using
import scala.util.control.NonFatal
def withRetry[A](maxAttempts: Int)(operation: => A): Option[A] = {
(1 to maxAttempts).view.map { attempt =>
try {
Some(operation)
} catch {
case NonFatal(e) =>
if (attempt == maxAttempts) {
println(s"All $maxAttempts attempts failed")
None
} else {
println(s"Attempt $attempt failed, retrying...")
Thread.sleep(1000 * attempt) // Exponential backoff
None
}
}
}.find(_.isDefined).flatten
}
// Usage with resource management
withRetry(3) {
Using(connectToDatabase()) { conn =>
executeQuery(conn, "SELECT COUNT(*) FROM users")
}.get
}Resource Pooling:
import scala.util.Using
import java.util.concurrent.BlockingQueue
class ResourcePool[R: Releasable](factory: () => R, maxSize: Int) {
private val pool: BlockingQueue[R] = new java.util.concurrent.LinkedBlockingQueue[R](maxSize)
def withResource[A](operation: R => A): A = {
val resource = Option(pool.poll()).getOrElse(factory())
try {
val result = operation(resource)
if (pool.remainingCapacity() > 0) {
pool.offer(resource)
} else {
implicitly[Releasable[R]].release(resource)
}
result
} catch {
case exception =>
implicitly[Releasable[R]].release(resource)
throw exception
}
}
}
// Usage
val dbPool = new ResourcePool(() => createDatabaseConnection(), 10)
dbPool.withResource { conn =>
executeQuery(conn, "SELECT * FROM products")
}Nested Resource Management:
import scala.util.Using
def processNestedResources(): Unit = {
Using(openArchive("data.zip")) { archive =>
archive.entries.foreach { entry =>
Using(archive.getInputStream(entry)) { entryStream =>
Using(new BufferedReader(new InputStreamReader(entryStream))) { reader =>
processFileContent(entry.getName, reader)
}
}
}
}
}The Using utility provides a robust foundation for resource management in Scala applications, ensuring that resources are properly cleaned up regardless of whether operations succeed or fail.