zio.blocks.scope
Scope: A compile-time safe resource management library using scope-local opaque types.
==Quick Start==
import zio.blocks.scope._
Scope.global.scoped { scope =>
import scope._
val db: $[Database] = allocate(Resource[Database])
val result: $[Int] = (scope $ db)(_.query("SELECT 1"))
result // Returns $[Int], unwrapped to Int at boundary
}
==Key Concepts==
- '''Scoped values''' (
scope.$[A]): Values tied to a scope, preventing escape - '''
import scope._''': Bring scope operations into lexical scope - '''
allocate(resource)''': Allocate a value in the current scope - '''
(scope $ value)(f)''': Apply a function to a scoped value - '''
scoped { s => ... }''': Create a child scope - '''
defer { ... }''': Register cleanup to run when scope closes
==How It Works==
Each scope has its own $[A] opaque type. Child scopes create structurally incompatible $[A] types, preventing sibling-scoped values from being mixed. Parent-scoped values can be lowered into child scopes via lower().
The .scoped method requires Unscoped[A] evidence on the return type, ensuring only pure data (not resources or closures) can escape.
Attributes
- See also
-
scope.Scope for scope types and operations scope.Resource for creating scoped values scope.Unscoped for types that can cross scope boundaries
Members list
Packages
Type members
Classlikes
This object was generated by sbt-buildinfo.
This object was generated by sbt-buildinfo.
Attributes
- Source
- BuildInfo.scala
- Supertypes
-
trait Singletontrait Producttrait Mirrortrait Serializabletrait Producttrait Equalsclass Objecttrait Matchableclass AnyShow all
- Self type
-
BuildInfo.type
Attributes
- Companion
- class
- Source
- DeferHandle.scala
- Supertypes
- Self type
-
DeferHandle.type
A handle returned by Scope.defer that allows cancelling a registered finalizer before the scope closes.
A handle returned by Scope.defer that allows cancelling a registered finalizer before the scope closes.
When scope.defer(cleanup) is called, the cleanup action is registered to run when the scope closes, and a DeferHandle is returned. The handle can be used to remove that finalizer early, attempting to prevent it from running when the scope closes. If cancellation races with scope closure, the finalizer may already be in the process of running. This is useful when a resource is explicitly released before the scope ends, and running the finalizer again would be unnecessary or harmful.
Attributes
- See also
-
Scope.defer for registering finalizers
- Companion
- object
- Source
- DeferHandle.scala
- Supertypes
Attributes
- Companion
- class
- Source
- Finalization.scala
- Supertypes
- Self type
-
Finalization.type
The result of running finalizers, collecting any errors that occurred.
The result of running finalizers, collecting any errors that occurred.
When a scope closes, each registered finalizer runs in LIFO order. Finalizers that throw are caught and their exceptions are collected into a Finalization. This type provides convenient methods for inspecting and re-throwing those errors.
Value parameters
- errors
-
the errors collected from running finalizers
Attributes
- Companion
- object
- Source
- Finalization.scala
- Supertypes
A handle for registering cleanup actions (finalizers).
A handle for registering cleanup actions (finalizers).
Finalizers run in LIFO order when the scope closes. This trait exposes only the defer capability, preventing user code from accessing scope internals like allocate or close.
Attributes
- Source
- Finalizer.scala
- Supertypes
- Known subtypes
Attributes
- Companion
- trait
- Source
- Resource.scala
- Supertypes
- Self type
-
Resource.type
A description of how to acquire and release a resource.
A description of how to acquire and release a resource.
Resources encapsulate both acquisition and finalization, tying a value's lifecycle to a Scope. When a resource is created via scope.allocate(resource), the scope registers any finalizers and ensures they run when the scope closes.
Resources are lazy—they describe ''what'' to do, not ''when''. Creation only happens when passed to a scope via scope.allocate(resource).
==Creating Resources==
Resource(=> a): By-name value; auto-registersclose()ifAutoCloseableResource.acquireRelease(acquire)(release): Explicit acquire/releaseResource.fromAutoCloseable(=> a): ForAutoCloseablesubtypesResource.from[T]macro: Derives from constructor
Type parameters
- A
-
the type of value this resource produces
Attributes
- See also
-
[[Scope.allocateA* Scope.allocate]] for using resources
Wire.toResourcefor converting wires - Example
-
Scope.global.scoped { scope => import scope._ val db = allocate(Resource.fromAutoCloseable(new Database())) (scope $ db)(_.query("SELECT 1")) } - Companion
- object
- Source
- Resource.scala
- Supertypes
- Self type
-
Resource[A]
A scope that manages resource lifecycle with compile-time verified safety.
A scope that manages resource lifecycle with compile-time verified safety.
==Closed-scope safety==
The operations allocate, open, and $ throw java.lang.IllegalStateException if called on a scope that has already closed. The exception message explains what went wrong and how to fix it. This prevents silent use-after-free bugs where a scope reference escapes its scoped { } block and a resource is accessed after its finalizers have run.
defer on a closed scope is silently ignored (no-op), and scoped on a closed scope creates a born-closed child.
Attributes
- Companion
- object
- Source
- Scope.scala
- Supertypes
- Known subtypes
- Self type
Companion object providing given instances for common types.
Companion object providing given instances for common types.
==Adding Custom Instances==
To mark your own type as safe to escape scopes, define a given instance:
case class UserId(value: Long)
object UserId {
given Unscoped[UserId] = new Unscoped[UserId] {}
}
Only add Unscoped instances for pure data types that don't hold resources. Resource types (streams, connections, handles) should NOT have instances.
Attributes
- Companion
- trait
- Source
- Unscoped.scala
- Supertypes
- Self type
-
Unscoped.type
Marker typeclass for types that can escape a scope unscoped.
Marker typeclass for types that can escape a scope unscoped.
Types with an Unscoped instance are considered "safe data" - they don't hold resources and can be freely extracted from a scope without tracking.
==Default Instances==
The following types have Unscoped instances by default:
- Primitives:
Int,Long,Short,Byte,Char,Boolean,Float,Double,Unit - Text:
String - Numeric:
BigInt,BigDecimal - Collections:
Array,List,Vector,Set,Seq,IndexedSeq,Iterable,Map(when elements areUnscoped) - Containers:
Option,Either(when elements areUnscoped) - Tuples: up to 4-tuples (when elements are
Unscoped) - Time:
java.time.*value types,scala.concurrent.duration.* - Other:
java.util.UUID, zio.blocks.chunk.Chunk
==Resource Types==
Types representing resources (streams, connections, handles, etc.) should NOT have Unscoped instances. This ensures they remain tracked by the scope system and cannot accidentally escape.
Type parameters
- A
-
the type that can escape scopes
Attributes
- Example
-
// Primitives escape freely (auto-unwrapped by $) Scope.global.scoped { s => import s._ (s $ stream)(_.read()) // returns Int directly since Int: Unscoped } // Resources stay scoped val body: $[InputStream] = (scope $ request)(_.body) // InputStream stays scoped (no Unscoped instance) - Companion
- object
- Source
- Unscoped.scala
- Supertypes
A recipe for constructing a service and its dependencies.
A recipe for constructing a service and its dependencies.
Wires are the building blocks of dependency injection in Scope. They describe how to construct a service given access to its dependencies via a context.
==Wire Types==
- Wire.Shared: Converted to a reference-counted Resource.shared (default)
- Wire.Unique: Converted to a fresh Resource.unique each time
==Creating Wires==
Use Wire.shared[T] and Wire.unique[T] macros for automatic derivation from constructors, or create wires manually for custom construction logic.
==Usage Example==
// Define a service with its wire
class Database(config: Config)
object Database {
val wire: Wire[Config, Database] = Wire.shared[Database]
}
// Create a unique wire for request-scoped services
class RequestHandler(db: Database)
object RequestHandler {
val wire: Wire[Database, RequestHandler] = Wire.unique[RequestHandler]
}
// Create a Resource from wires
val appResource = Resource.from[App](
Wire(Config("localhost", 8080)),
Wire.shared[Database]
)
Type parameters
- In
-
the dependencies required (contravariant - accepts supertypes)
- Out
-
the service(s) produced (covariant - produces subtypes)
Attributes
- Companion
- object
- Source
- Wire.scala
- Supertypes
- Known subtypes
Companion object providing wire factory methods and wire classes.
Companion object providing wire factory methods and wire classes.
Attributes
- Companion
- trait
- Source
- Wire.scala
- Supertypes
- Self type
-
Wire.type
Deprecated classlikes
Type parameters
- Stack
-
the HList-style stack to search within
- T
-
the type to search for in the stack
Attributes
- Companion
- object
- Deprecated
-
[Since version 0.1.0]InStack is no longer used in the new Scope designInStack is no longer used in the new Scope design. Dependencies are resolved via Resource and Wire, not via HList type-level search.
- Source
- InStack.scala
- Supertypes
Attributes
- Companion
- trait
- Deprecated
-
[Since version 0.1.0]InStack is no longer used in the new Scope designInStack is no longer used in the new Scope design.
- Source
- InStack.scala
- Supertypes
- Self type
-
InStack.type
Value members
Concrete methods
Registers a finalizer to run when the current scope closes.
Registers a finalizer to run when the current scope closes.
Finalizers run in LIFO order (last registered runs first). If a finalizer throws, subsequent finalizers still run.
Value parameters
- fin
-
the finalizer capability to register cleanup with
- finalizer
-
a by-name expression to execute on scope close
Attributes
- Example
-
Scope.global.scoped { scope => val resource = acquire() scope.defer { resource.release() } // use resource... } - Source
- package.scala