@zio.dev/zio-blocks 0.0.1 → 0.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/scope.md CHANGED
@@ -2,11 +2,14 @@
2
2
 
3
3
  `zio.blocks.scope` provides **compile-time verified resource safety** for synchronous code by tagging values with an unnameable, type-level **scope identity**. Values allocated in a scope can only be used when you hold a compatible `Scope`, and values allocated in a *child* scope cannot be returned to the parent in a usable form.
4
4
 
5
+ **Structured scopes.** Scopes follow the structured-concurrency philosophy: child scopes are nested within parent scopes, resources are tied to the lifetime of the scope that allocated them, and cleanup happens deterministically when the scope exits (finalizers run LIFO). This "nesting = lifetime" structure provides clear ownership boundaries in addition to compile-time leak prevention.
6
+
5
7
  If you've used `try/finally`, `Using`, or ZIO `Scope`, this library lives in the same problem space, but it focuses on:
6
8
 
7
9
  - **Compile-time prevention of scope leaks**
8
- - **Zero runtime overhead for the scoped tag** (`A @@ S` is represented as `A`)
10
+ - **Zero-cost opaque type** (`$[A]` is the scoped type, equal to `A` at runtime)
9
11
  - **Simple, synchronous lifecycle management** (finalizers run LIFO on scope close)
12
+ - **Eager evaluation** (all operations execute immediately, no deferred thunks)
10
13
 
11
14
  ---
12
15
 
@@ -14,23 +17,24 @@ If you've used `try/finally`, `Using`, or ZIO `Scope`, this library lives in the
14
17
 
15
18
  - [Quick start](#quick-start)
16
19
  - [Core concepts](#core-concepts)
17
- - [1) `Scope[ParentTag, Tag]`](#1-scopeparenttag-tag)
18
- - [2) Scoped values: `A @@ S`](#2-scoped-values-a--s)
20
+ - [1) `Scope`](#1-scope)
21
+ - [2) Scoped values: `$[+A]`](#2-scoped-values-a)
19
22
  - [3) `Resource[A]`: acquisition + finalization](#3-resourcea-acquisition--finalization)
20
- - [4) `Scoped[Tag, A]`: deferred computations](#4-scopedtag-a-deferred-computations)
21
- - [5) `ScopeEscape` and `Unscoped`: what may escape](#5-scopeescape-and-unscoped-what-may-escape)
23
+ - [4) `Unscoped`: marking pure data types](#4-unscoped-marking-pure-data-types)
24
+ - [5) `lower`: accessing parent-scoped values](#5-lower-accessing-parent-scoped-values)
22
25
  - [6) `Wire[-In, +Out]`: dependency recipes](#6-wire-in-out-dependency-recipes)
23
- - [7) `Wireable[Out]`: DI for traits/abstract classes](#7-wireableout-di-for-traitsabstract-classes)
24
26
  - [Safety model (why leaking is prevented)](#safety-model-why-leaking-is-prevented)
25
27
  - [Usage examples](#usage-examples)
26
28
  - [Allocating and using a resource](#allocating-and-using-a-resource)
27
29
  - [Nested scopes (child can use parent, not vice versa)](#nested-scopes-child-can-use-parent-not-vice-versa)
28
- - [Building a `Scoped` program (map/flatMap)](#building-a-scoped-program-mapflatmap)
30
+ - [Chaining resource acquisition](#chaining-resource-acquisition)
29
31
  - [Registering cleanup manually with `defer`](#registering-cleanup-manually-with-defer)
32
+ - [Classes with `Finalizer` parameters](#classes-with-finalizer-parameters)
30
33
  - [Dependency injection with `Wire` + `Context`](#dependency-injection-with-wire--context)
31
- - [Supplying dependencies with `Resource.from[T](wire1, wire2, ...)`](#supplying-dependencies-with-resourcefromtwire1-wire2-)
32
- - [DI for traits via `Wireable`](#di-for-traits-via-wireable)
34
+ - [Dependency injection with `Resource.from[T](wires*)`](#dependency-injection-with-resourcefromtwires)
35
+ - [Injecting traits via subtype wires](#injecting-traits-via-subtype-wires)
33
36
  - [Interop escape hatch: `leak`](#interop-escape-hatch-leak)
37
+ - [Common compile errors](#common-compile-errors)
34
38
  - [API reference (selected)](#api-reference-selected)
35
39
 
36
40
  ---
@@ -46,80 +50,113 @@ final class Database extends AutoCloseable {
46
50
  }
47
51
 
48
52
  Scope.global.scoped { scope =>
49
- val db: Database @@ scope.Tag =
50
- scope.allocate(Resource(new Database))
53
+ import scope._
51
54
 
52
- val result: String =
53
- scope.$(db)(_.query("SELECT 1"))
55
+ val db: $[Database] = allocate(Resource(new Database))
54
56
 
55
- println(result)
57
+ // scope.use applies a function to the scoped value, returning a scoped result
58
+ val result: $[String] = scope.use(db)(_.query("SELECT 1"))
59
+ println(result) // $[String] = String at runtime, prints directly
56
60
  }
57
61
  ```
58
62
 
59
63
  Key things to notice:
60
64
 
61
- - `scope.allocate(...)` returns a **scoped** value: `Database @@ scope.Tag`
62
- - You **cannot** call `db.query(...)` directly (methods are intentionally hidden)
63
- - You must use `scope.$(db)(...)` or build a `Scoped` computation
65
+ - `allocate(...)` returns a **scoped** value of type `$[Database]` (the path-dependent type of the enclosing scope)
66
+ - `$[A] = A` at runtime zero-cost opaque type, no boxing
67
+ - All operations are **eager** values are computed immediately, no lazy thunks
68
+ - Use `scope.use(value)(f)` to work with scoped values; returns `$[B]`
64
69
  - When the `scoped { ... }` block exits, finalizers run **LIFO** and errors are handled safely
70
+ - The `scoped` method requires `Unscoped[A]` evidence on the return type
65
71
 
66
72
  ---
67
73
 
68
74
  ## Core concepts
69
75
 
70
- ### 1) `Scope[ParentTag, Tag]`
76
+ ### 1) `Scope`
71
77
 
72
- A `Scope` manages finalizers and ties values to a *type-level identity* called a **Tag**.
78
+ `Scope` is a `sealed abstract class` with **no** type parameters. It manages finalizers and ties values to a *type-level identity* via abstract type members.
73
79
 
74
- - `Scope[ParentTag, Tag]` has **two** type parameters:
75
- - `ParentTag`: the parent scope's tag (capability boundary)
76
- - `Tag <: ParentTag`: this scope's unique identity (used to tag values)
80
+ - **`type $[+A]`** a path-dependent opaque type that tags values to this scope. Covariant in `A`. Equal to `A` at runtime (zero-cost).
81
+ - **`type Parent <: Scope`** — the parent scope's type.
82
+ - **`val parent: Parent`** reference to the parent scope.
77
83
 
78
- Every `Scope` also exposes a *path-dependent* member type:
84
+ Each scope instance exposes its own `$[+A]`, so a parent's `$[Database]` is a different type than a child's `$[Database]`, even though both equal `Database` at runtime.
79
85
 
80
86
  ```scala
81
- type Tag = Tag0
87
+ type $[+A] // = A at runtime (zero-cost)
82
88
  ```
83
89
 
84
90
  So in code you'll typically write:
85
91
 
86
92
  ```scala
87
93
  Scope.global.scoped { scope =>
88
- val x: Something @@ scope.Tag = ???
94
+ import scope._
95
+ val x: $[Something] = ??? // or scope.$[Something]
89
96
  }
90
97
  ```
91
98
 
99
+ Child scopes are represented by `Scope.Child[P <: Scope]`, a `final class` nested in the `Scope` companion object.
100
+
92
101
  #### Global scope
93
102
 
94
- `Scope.global` is the root of the tag hierarchy:
103
+ `Scope.global` is the root of the scope hierarchy:
95
104
 
96
105
  ```scala
97
106
  object Scope {
98
- type GlobalTag
99
- lazy val global: Scope[GlobalTag, GlobalTag]
107
+ object global extends Scope {
108
+ type $[+A] = A
109
+ type Parent = global.type
110
+ val parent: Parent = this
111
+ }
100
112
  }
101
113
  ```
102
114
 
103
115
  - The global scope is intended to live for the lifetime of the process.
104
116
  - Its finalizers run on JVM shutdown.
105
- - Values allocated in `Scope.global` typically **escape** as raw values via `ScopeEscape` (see below).
106
117
 
107
118
  ---
108
119
 
109
- ### 2) Scoped values: `A @@ S`
120
+ ### 2) Scoped values: `$[+A]`
110
121
 
111
- `A @@ S` means: "a value of type `A` that is locked to scope tag `S`".
122
+ `$[+A]` (or `scope.$[A]` in type annotations) is a path-dependent opaque type representing a value of type `A` that is locked to a specific scope. It is covariant in `A`.
112
123
 
113
- - **Runtime representation:** just `A` (no wrapper allocation)
114
- - **Key effect:** methods on `A` are hidden, so you can't call `a.method` without proving scope access
124
+ - **Runtime representation:** `$[A] = A` — zero-cost opaque type, no boxing or wrapping
125
+ - **Key effect:** methods on `A` are hidden at the type level; you can't call `a.method` directly
126
+ - **All operations are eager:** `allocate(resource)` acquires the resource **immediately** and returns a scoped value
115
127
  - **Access paths:**
116
- - `scope.$(a)(f)` to use a scoped value immediately
117
- - `a.map / a.flatMap` to build a `Scoped` computation, then run it via `scope(scoped)`
128
+ - `scope.use(a)(f)` to apply a function and get `$[B]`
129
+
130
+ #### `ScopedOps`: `map` and `flatMap` on `$[A]`
131
+
132
+ `Scope` provides an implicit class `ScopedOps[A]` that adds `map` and `flatMap` to `$[A]` values, enabling for-comprehension syntax:
133
+
134
+ ```scala
135
+ Scope.global.scoped { scope =>
136
+ import scope._
137
+
138
+ val x: $[Int] = $(42)
139
+ val y: $[String] = x.map(_.toString)
140
+ val z: $[String] = x.flatMap(v => $(s"value: $v"))
141
+ }
142
+ ```
143
+
144
+ - `sa.map(f: A => B): $[B]` — applies `f` to the unwrapped value, re-wraps the result
145
+ - `sa.flatMap(f: A => $[B]): $[B]` — applies `f` to the unwrapped value (where `f` returns a scoped value)
146
+ - All operations are **eager** (zero-cost)
147
+
148
+ #### Scala 2 note
149
+
150
+ In Scala 2, the `scoped` method must be called with a lambda literal. Passing a variable or method reference is not supported due to macro limitations:
118
151
 
119
- #### Scala 3 vs Scala 2 note
152
+ ```scala
153
+ // ✅ OK: lambda literal
154
+ Scope.global.scoped { scope => ... }
120
155
 
121
- - In **Scala 3**, `@@` is implemented as an `opaque` type.
122
- - In **Scala 2**, the library emulates the same "opaque-like" behavior using the *module pattern* (still zero-overhead at runtime).
156
+ // ERROR in Scala 2 (works in Scala 3):
157
+ val f: Scope.Child[_] => Any = scope => ...
158
+ Scope.global.scoped(f)
159
+ ```
123
160
 
124
161
  ---
125
162
 
@@ -128,7 +165,7 @@ object Scope {
128
165
  `Resource[A]` describes how to **acquire** an `A` and how to **release** it when a scope closes. It is intentionally lazy: you *describe what to do*, and allocation happens only through:
129
166
 
130
167
  ```scala
131
- scope.allocate(resource)
168
+ allocate(resource)
132
169
  ```
133
170
 
134
171
  Common constructors:
@@ -139,12 +176,12 @@ Common constructors:
139
176
  - Explicit lifecycle.
140
177
  - `Resource.fromAutoCloseable(thunk)`
141
178
  - A type-safe helper for `AutoCloseable`.
142
- - `Resource.from[T]` (macro)
143
- - Derives a resource from `T`'s constructor.
144
- - If `T` is `AutoCloseable`, registers `close()` automatically.
145
- - If `T` has dependencies, either:
146
- - use `Wire` + `toResource(deps)`, or
147
- - use `Resource.from[T](wire1, wire2, ...)` (see below).
179
+ - `Resource.from[T](wires*)` (macro)
180
+ - The primary entry point for dependency injection.
181
+ - Resolves `T` and all its dependencies into a single `Resource[T]`.
182
+ - Auto-creates missing wires using `Wire.shared` for concrete classes.
183
+ - Requires explicit wires for: primitives, functions, collections, and abstract types.
184
+ - If `T` or any dependency is `AutoCloseable`, registers `close()` automatically.
148
185
 
149
186
  #### Resource "sharing" vs "uniqueness"
150
187
 
@@ -162,93 +199,138 @@ Common constructors:
162
199
 
163
200
  ---
164
201
 
165
- ### 4) `Scoped[Tag, A]`: deferred computations
202
+ ### 4) `Unscoped`: marking pure data types
166
203
 
167
- `Scoped[-Tag, +A]` represents a computation that produces `A`, but can only be executed by a scope whose tag is compatible with `Tag`.
204
+ The `Unscoped[A]` typeclass marks types as pure data that don't hold resources. The `scoped` method requires `Unscoped[A]` evidence on the return type to ensure only safe values can exit a scope.
168
205
 
169
- Execution happens via:
206
+ **Built-in Unscoped types:**
207
+ - Primitives: `Int`, `Long`, `Boolean`, `Double`, etc.
208
+ - `String`, `Unit`, `Nothing`
209
+ - Collections of Unscoped types
170
210
 
211
+ **Custom Unscoped types:**
171
212
  ```scala
172
- scope(scopedComputation)
213
+ // Scala 3:
214
+ case class Config(debug: Boolean)
215
+ object Config {
216
+ given Unscoped[Config] = Unscoped.derived
217
+ }
218
+
219
+ // Scala 2:
220
+ case class Config(debug: Boolean)
221
+ object Config {
222
+ implicit val unscopedConfig: Unscoped[Config] = Unscoped.derived[Config]
223
+ }
173
224
  ```
174
225
 
175
- How to build them:
226
+ **Allowed return types from `scoped`:**
176
227
 
177
- - From scoped values:
178
- - `val s: Scoped[S, B] = (a: A @@ S).map(f)`
179
- - `flatMap` composes scoped values while tracking combined requirements
180
- - Or directly:
181
- - `Scoped.create(() => ...)` (advanced/internal style)
228
+ - **`Unscoped` types**: Pure data that can safely exit
229
+ - **`Nothing`**: For blocks that throw
182
230
 
183
- `Scoped` is contravariant in `Tag`, which is what allows a **child** scope (more specific tag) to run computations that only require a **parent** tag.
231
+ **Rejected return types (no Unscoped instance):**
232
+
233
+ - **Closures**: `() => A` could capture the child scope
234
+ - **Scoped values**: `$[A]` would be use-after-close
235
+ - **The scope itself**: Would allow operations after close
236
+
237
+ ```scala
238
+ Scope.global.scoped { parent =>
239
+ import parent._
240
+
241
+ // ✅ OK: String is Unscoped
242
+ val result: String = scoped { child =>
243
+ "hello"
244
+ }
245
+
246
+ // ❌ COMPILE ERROR: $[Database] has no Unscoped instance
247
+ // val escaped = scoped { child =>
248
+ // import child._
249
+ // allocate(Resource(new Database)) // $[Database] can't escape
250
+ // }
251
+ }
252
+ ```
184
253
 
185
254
  ---
186
255
 
187
- ### 5) `ScopeEscape` and `Unscoped`: what may escape
256
+ ### 5) `lower`: accessing parent-scoped values
257
+
258
+ When working in a child scope, you may need to access values allocated in a parent scope. Use `lower(parentValue)` to "lower" a parent-scoped value into the child scope:
188
259
 
189
- Whenever you access a scoped value via:
260
+ ```scala
261
+ Scope.global.scoped { parent =>
262
+ import parent._
190
263
 
191
- - `scope.$(value)(f)`, or
192
- - `scope(scopedComputation)`,
264
+ val parentDb: $[Database] = allocate(Resource(new Database))
193
265
 
194
- …the return type is controlled by `ScopeEscape[A, S]`, which decides whether a result:
266
+ scoped { child =>
267
+ import child._
195
268
 
196
- - escapes as raw `A`, or
197
- - remains tracked as `A @@ S`.
269
+ // Use lower() to access parent-scoped value in child scope
270
+ val db: $[Database] = lower(parentDb)
271
+ child.use(db)(_.query("SELECT 1"))
198
272
 
199
- Rule of thumb:
273
+ "done"
274
+ }
275
+ }
276
+ ```
200
277
 
201
- - Pure data (e.g. `Int`, `String`, small case classes you mark `Unscoped`) should escape as raw values.
202
- - Resource-like values should remain scoped unless you explicitly `leak`.
278
+ The `lower` operation is necessary because each scope has its own `$[A]` opaque type. A parent's `$[A]` is a different type than a child's `$[A]`, even though both equal `A` at runtime.
203
279
 
204
280
  ---
205
281
 
206
282
  ### 6) `Wire[-In, +Out]`: dependency recipes
207
283
 
208
- `Wire` is a recipe for constructing services, commonly used for dependency injection.
284
+ `Wire` is a recipe for constructing services. It describes **how** to build a service given its dependencies, but does not resolve those dependencies itself.
209
285
 
210
286
  - `In` is the required dependencies (provided as a `Context[In]`)
211
287
  - `Out` is the produced service
212
288
 
213
289
  There are two wire flavors:
214
290
 
215
- - `Wire.Shared`: a shared recipe
216
- - `Wire.Unique`: a unique recipe
291
+ - `Wire.Shared`: produces a shared (memoized) instance
292
+ - `Wire.Unique`: produces a fresh instance each time
217
293
 
218
- **Important clarification:** `Wire` itself is just a recipe. The actual memoization/sharing behavior happens when you convert the wire into a `Resource`:
294
+ **Important clarification:** `Wire` itself is just a recipe. The sharing/uniqueness behavior is realized when the wire is used inside `Resource.from`, which composes `Resource.Shared` or `Resource.Unique` instances accordingly.
219
295
 
220
- ```scala
221
- val r: Resource[Out] = wire.toResource(deps)
222
- val out: Out @@ scope.Tag = scope.allocate(r)
223
- ```
296
+ #### Creating wires
224
297
 
225
- - `Wire.Shared#toResource` produces a `Resource.Shared`, which is where the reference-counted sharing is implemented.
226
- - `Wire.Unique#toResource` produces a `Resource.Unique`.
298
+ There are exactly **3 macro entry points**:
227
299
 
228
- Macros available at package level:
300
+ | Macro | Purpose |
301
+ |-------|---------|
302
+ | `Wire.shared[T]` | Create a shared wire from `T`'s constructor |
303
+ | `Wire.unique[T]` | Create a unique wire from `T`'s constructor |
304
+ | `Resource.from[T](wires*)` | Wire up `T` and all dependencies into a `Resource` |
229
305
 
230
- - `shared[T]`: derive a shared wire from `T`'s constructor (or from a `Wireable[T]` if present)
231
- - `unique[T]`: derive a unique wire
306
+ For wrapping pre-existing values:
232
307
 
233
- ---
308
+ - `Wire(value)` — wraps a value; if `AutoCloseable`, registers `close()` automatically
234
309
 
235
- ### 7) `Wireable[Out]`: DI for traits/abstract classes
310
+ #### How `Resource.from[T](wires*)` works
236
311
 
237
- `shared[T]` / `unique[T]` can derive wires from **concrete classes** with constructors. But traits and abstract classes are not instantiable, so you need a way to tell the macros "when someone asks for `T`, build it like *this*".
312
+ 1. **Collect wires**: Uses explicit wires when provided, otherwise auto-creates with `Wire.shared`
313
+ 2. **Validate**: Checks for cycles, unmakeable types, duplicate providers
314
+ 3. **Topological sort**: Orders dependencies so leaves are allocated first
315
+ 4. **Generate composition**: Produces a `Resource[T]` via flatMap chains
238
316
 
239
- That's what `Wireable[T]` is: a typeclass that supplies a `Wire` for a service.
317
+ Key insight: **Compose Resources, don't accumulate values.** Each wire becomes a `Resource`, and they are composed via `flatMap`. This correctly preserves:
240
318
 
241
- Typical use:
319
+ - **Sharing**: Same `Resource.Shared` instance → same value (even in diamond patterns)
320
+ - **Uniqueness**: `Resource.Unique` → fresh value per injection site
242
321
 
243
- - Define a `Wireable[MyTrait]` in `MyTrait`'s companion object.
244
- - `shared[MyTrait]` or `unique[MyTrait]` will pick it up automatically.
322
+ #### Subtype resolution
245
323
 
246
- This is especially useful when you want to inject an interface but construct a concrete implementation (and still register finalizers correctly).
324
+ When `Resource.from` needs a dependency of type `Service`, it will accept a wire whose output is a subtype (e.g., `Wire.shared[LiveService]` where `LiveService extends Service`). This enables trait injection without extra boilerplate.
325
+
326
+ If the same concrete wire satisfies multiple types (e.g., `Service` and `LiveService`), only **one instance** is created and reused for both.
247
327
 
248
328
  ---
249
329
 
250
330
  ## Safety model (why leaking is prevented)
251
331
 
332
+ **Pragmatic safety.** The type-level tagging prevents *accidental* scope misuse in normal code, but it is not a security boundary. A determined developer can bypass it via `leak` (which emits a compiler warning), unsafe casts (`asInstanceOf`), or storing scoped references in mutable state (`var`). The guarantees are "good enough" to catch mistakes in regular usage, not protection against intentional circumvention.
333
+
252
334
  The library prevents scope leaks via two reinforcing mechanisms:
253
335
 
254
336
  ### A) Existential child tags (fresh, unnameable types)
@@ -257,20 +339,24 @@ Child scopes are created with:
257
339
 
258
340
  ```scala
259
341
  Scope.global.scoped { scope =>
260
- scope.scoped { child =>
342
+ import scope._
343
+ scoped { child =>
344
+ import child._
261
345
  // allocate in child
262
346
  }
263
347
  }
264
348
  ```
265
349
 
266
- The child scope has an existential tag (fresh per invocation). You can allocate in the child, but you can't return those values to the parent in a usable form because the parent cannot name (or satisfy) the child tag.
350
+ The child scope has a fresh, unnameable `$[A]` type (created per invocation). You can allocate in the child, but you can't return those values to the parent in a usable form because the parent cannot name (or satisfy) the child's `$[A]` type.
267
351
 
268
352
  Compile-time safety is verified in tests, e.g.:
269
353
  `ScopeCompileTimeSafetyScala3Spec`.
270
354
 
271
- ### B) Tag invariance + "opaque-like" `@@` blocks subtyping escape
355
+ ### B) Opaque types prevent escape
272
356
 
273
- Even if you try to "widen" a child-tagged value to a parent-tagged value, invariance and hidden members prevent it from typechecking. The only sanctioned access route is through `scope.$` / `scope.apply`, which require tag evidence.
357
+ Each scope defines its own `$[A]` opaque type. Even though `$[A] = A` at runtime, the compiler treats each scope's `$[A]` as distinct. A child's `$[Database]` is a different type than the parent's `$[Database]`.
358
+
359
+ Additionally, the opaque type hides `A`'s methods at the type level — you can't call `db.query(...)` directly on a `$[Database]`. Access routes are `scope.use(value)(f)` and the `ScopedOps` methods (`map`, `flatMap`) for for-comprehensions.
274
360
 
275
361
  ---
276
362
 
@@ -287,12 +373,13 @@ final class FileHandle(path: String) extends AutoCloseable {
287
373
  }
288
374
 
289
375
  Scope.global.scoped { scope =>
290
- val h = scope.allocate(Resource(new FileHandle("data.txt")))
376
+ import scope._
291
377
 
292
- val contents: String =
293
- scope.$(h)(_.readAll())
378
+ val h: $[FileHandle] = allocate(Resource(new FileHandle("data.txt")))
294
379
 
295
- println(contents)
380
+ // scope.use applies function to scoped value, returns $[String]
381
+ val contents: $[String] = scope.use(h)(_.readAll())
382
+ println(contents) // $[String] = String at runtime
296
383
  }
297
384
  ```
298
385
 
@@ -304,64 +391,84 @@ Scope.global.scoped { scope =>
304
391
  import zio.blocks.scope._
305
392
 
306
393
  Scope.global.scoped { parent =>
307
- val parentDb = parent.allocate(Resource(new Database))
394
+ import parent._
395
+
396
+ val parentDb: $[Database] = allocate(Resource(new Database))
397
+
398
+ scoped { child =>
399
+ import child._
308
400
 
309
- parent.scoped { child =>
310
- // child can use parent-scoped values:
311
- val ok: String = child.$(parentDb)(_.query("SELECT 1"))
312
- println(ok)
401
+ // Use lower() to access parent-scoped values in child scope:
402
+ val db: $[Database] = lower(parentDb)
403
+ println(child.use(db)(_.query("SELECT 1")))
313
404
 
314
- val childDb = child.allocate(Resource(new Database))
405
+ val childDb: $[Database] = allocate(Resource(new Database))
315
406
 
316
407
  // You can use childDb *inside* the child:
317
- val ok2: String = child.$(childDb)(_.query("SELECT 2"))
318
- println(ok2)
408
+ println(child.use(childDb)(_.query("SELECT 2")))
319
409
 
320
- // But you cannot return childDb to the parent in a usable way:
321
- // childDb : Database @@ child.Tag
322
- // parent cannot prove parent.Tag <:< child.Tag
410
+ // But you cannot return childDb to the parent:
411
+ // $[Database] has no Unscoped instance — compile error
412
+
413
+ // Return an Unscoped value
414
+ "done"
323
415
  }
324
416
 
325
417
  // parentDb is still usable here:
326
- val stillOk = parent.$(parentDb)(_.query("SELECT 3"))
327
- println(stillOk)
418
+ println(parent.use(parentDb)(_.query("SELECT 3")))
328
419
  }
329
420
  ```
330
421
 
331
422
  ---
332
423
 
333
- ### Building a `Scoped` program (map/flatMap)
424
+ ### Chaining resource acquisition
425
+
426
+ Since `$[A]` supports `map` and `flatMap` via `ScopedOps`, you can chain resource acquisitions in for-comprehensions:
334
427
 
335
428
  ```scala
336
429
  import zio.blocks.scope._
337
430
 
431
+ class Pool extends AutoCloseable {
432
+ def lease(): Connection = new Connection
433
+ def close(): Unit = println("pool closed")
434
+ }
435
+
436
+ class Connection extends AutoCloseable {
437
+ def query(sql: String): String = s"result: $sql"
438
+ def close(): Unit = println("connection closed")
439
+ }
440
+
338
441
  Scope.global.scoped { scope =>
339
- val db = scope.allocate(Resource(new Database))
442
+ import scope._
340
443
 
341
- val program: Scoped[scope.Tag, String] =
342
- for {
343
- a <- db.map(_.query("SELECT 1"))
344
- b <- db.map(_.query("SELECT 2"))
345
- } yield s"$a | $b"
444
+ // Chain allocations in a for-comprehension:
445
+ // flatMap unwraps $[Pool] to Pool, so pool.lease() returns a raw Connection
446
+ val result: $[String] = for {
447
+ pool <- allocate(Resource.from[Pool])
448
+ conn <- allocate(Resource(pool.lease()))
449
+ } yield conn.query("SELECT 1")
346
450
 
347
- val result: String = scope(program)
348
451
  println(result)
349
452
  }
453
+ // Output: result: SELECT 1
454
+ // Then: connection closed, pool closed (LIFO)
350
455
  ```
351
456
 
352
457
  ---
353
458
 
354
459
  ### Registering cleanup manually with `defer`
355
460
 
356
- Use `scope.defer` when you already have a value and just need to register cleanup.
461
+ Use `defer` when you already have a value and just need to register cleanup.
357
462
 
358
463
  ```scala
359
464
  import zio.blocks.scope._
360
465
 
361
466
  Scope.global.scoped { scope =>
467
+ import scope._
468
+
362
469
  val handle = new java.io.ByteArrayInputStream(Array[Byte](1, 2, 3))
363
470
 
364
- scope.defer { handle.close() }
471
+ defer { handle.close() }
365
472
 
366
473
  val firstByte = handle.read()
367
474
  println(firstByte)
@@ -374,7 +481,9 @@ There is also a package-level helper `defer` that only requires a `Finalizer`:
374
481
  import zio.blocks.scope._
375
482
 
376
483
  Scope.global.scoped { scope =>
484
+ import scope._
377
485
  given Finalizer = scope
486
+
378
487
  defer { println("cleanup") }
379
488
  }
380
489
  ```
@@ -390,7 +499,7 @@ import zio.blocks.scope._
390
499
 
391
500
  class ConnectionPool(config: Config)(implicit finalizer: Finalizer) {
392
501
  private val pool = createPool(config)
393
- finalizer.defer { pool.shutdown() }
502
+ finalizer.defer { pool.shutdown() } // or: defer { ... } with import zio.blocks.scope._
394
503
 
395
504
  def getConnection(): Connection = pool.acquire()
396
505
  }
@@ -399,37 +508,40 @@ class ConnectionPool(config: Config)(implicit finalizer: Finalizer) {
399
508
  val resource = Resource.from[ConnectionPool](Wire(Config("jdbc://localhost")))
400
509
 
401
510
  Scope.global.scoped { scope =>
402
- val pool = scope.allocate(resource)
511
+ import scope._
512
+ val pool = allocate(resource)
403
513
  // pool.shutdown() will be called when scope closes
404
514
  }
405
515
  ```
406
516
 
407
517
  Why `Finalizer` instead of `Scope`?
408
518
  - `Finalizer` is the minimal interface—it only has `defer`
409
- - Classes that need cleanup should not have access to `allocate` or `$`
519
+ - Classes that need cleanup should not have access to `allocate` or `use`
410
520
  - The macros pass a `Finalizer` at runtime, so declaring `Scope` would be misleading
411
521
 
412
522
  ---
413
523
 
414
524
  ### Dependency injection with `Wire` + `Context`
415
525
 
526
+ For manual wiring (when you already have dependencies assembled), use `wire.toResource(ctx)`:
527
+
416
528
  ```scala
417
529
  import zio.blocks.scope._
418
530
  import zio.blocks.context.Context
419
531
 
420
532
  final case class Config(debug: Boolean)
421
533
 
422
- val w: Wire.Shared[Boolean, Config] = shared[Config]
534
+ val w: Wire.Shared[Boolean, Config] = Wire.shared[Config]
423
535
  val deps: Context[Boolean] = Context[Boolean](true)
424
536
 
425
537
  Scope.global.scoped { scope =>
426
- val cfg: Config @@ scope.Tag =
427
- scope.allocate(w.toResource(deps))
538
+ import scope._
428
539
 
429
- val debug: Boolean =
430
- scope.$(cfg)(_.debug) // Boolean typically escapes
540
+ val cfg: $[Config] = allocate(w.toResource(deps))
431
541
 
432
- println(debug)
542
+ val debug: $[Boolean] = scope.use(cfg)(_.debug)
543
+
544
+ println(debug) // $[Boolean] = Boolean at runtime
433
545
  }
434
546
  ```
435
547
 
@@ -438,107 +550,120 @@ Sharing vs uniqueness at the wire level:
438
550
  ```scala
439
551
  import zio.blocks.scope._
440
552
 
441
- val ws = shared[Config] // shared recipe; sharing happens via Resource.Shared when allocated
442
- val wu = unique[Config] // unique recipe; each allocation is fresh
553
+ val ws = Wire.shared[Config] // shared recipe; sharing happens via Resource.Shared when allocated
554
+ val wu = Wire.unique[Config] // unique recipe; each allocation is fresh
443
555
  ```
444
556
 
445
557
  ---
446
558
 
447
- ### Supplying dependencies with `Resource.from[T](wire1, wire2, ...)`
559
+ ### Dependency injection with `Resource.from[T](wires*)`
448
560
 
449
- `Resource.from[T]` can also be used as a "standalone mini graph" by providing wires for constructor dependencies.
561
+ `Resource.from[T](wires*)` is the **primary entry point** for dependency injection. It resolves `T` and all its dependencies into a single `Resource[T]`.
450
562
 
451
563
  ```scala
452
564
  import zio.blocks.scope._
453
- import zio.blocks.context.Context
454
565
 
455
566
  final case class Config(url: String)
456
567
 
457
- trait Logger {
458
- def info(msg: String): Unit
568
+ final class Logger {
569
+ def info(msg: String): Unit = println(msg)
459
570
  }
460
571
 
461
- final class ConsoleLogger extends Logger {
462
- def info(msg: String): Unit = println(msg)
572
+ final class Database(cfg: Config) extends AutoCloseable {
573
+ def query(sql: String): String = s"[${cfg.url}] $sql"
574
+ def close(): Unit = println("database closed")
463
575
  }
464
576
 
465
- final class Service(cfg: Config, logger: Logger) extends AutoCloseable {
466
- def run(): Unit = logger.info(s"running with ${cfg.url}")
577
+ final class Service(db: Database, logger: Logger) extends AutoCloseable {
578
+ def run(): Unit = logger.info(s"running with ${db.query("SELECT 1")}")
467
579
  def close(): Unit = println("service closed")
468
580
  }
469
581
 
470
- // Provide wires for *all* dependencies of Service:
582
+ // Only provide leaf values (primitives, configs) - the rest is auto-wired:
471
583
  val serviceResource: Resource[Service] =
472
584
  Resource.from[Service](
473
- Wire(Config("jdbc:postgresql://localhost/db")),
474
- Wire(new ConsoleLogger: Logger)
585
+ Wire(Config("jdbc:postgresql://localhost/db"))
475
586
  )
476
587
 
477
588
  Scope.global.scoped { scope =>
478
- val svc = scope.allocate(serviceResource)
479
- scope.$(svc)(_.run())
589
+ import scope._
590
+ val svc: $[Service] = allocate(serviceResource)
591
+ scope.use(svc)(_.run())
480
592
  }
593
+ // Output: running with [jdbc:postgresql://localhost/db] SELECT 1
594
+ // Then: service closed, database closed (LIFO order)
481
595
  ```
482
596
 
483
- Notes:
484
- - All dependencies of `T` must be covered by the provided wires, otherwise you get a compile-time error.
485
- - If `T` is `AutoCloseable`, `close()` is registered automatically.
597
+ **What you must provide:**
598
+ - Leaf values: primitives, configs, pre-existing instances via `Wire(value)`
599
+ - Abstract types: traits/abstract classes via `Wire.shared[ConcreteImpl]`
600
+ - Overrides: when you want `unique` instead of the default `shared`
601
+
602
+ **What is auto-created:**
603
+ - Concrete classes with accessible primary constructors (default: `Wire.shared`)
486
604
 
487
605
  ---
488
606
 
489
- ### DI for traits via `Wireable`
607
+ ### Injecting traits via subtype wires
490
608
 
491
- When you want to inject a trait (or abstract class), define a `Wireable` in the companion so `shared[T]` / `unique[T]` can resolve it.
609
+ When a dependency is a trait or abstract class, provide a wire for a concrete implementation:
492
610
 
493
611
  ```scala
494
612
  import zio.blocks.scope._
495
- import zio.blocks.context.Context
496
613
 
497
- trait DatabaseApi {
498
- def query(sql: String): String
614
+ trait Logger {
615
+ def info(msg: String): Unit
499
616
  }
500
617
 
501
- final class LiveDatabaseApi(cfg: Config) extends DatabaseApi with AutoCloseable {
502
- def query(sql: String): String = s"[${cfg.url}] $sql"
503
- def close(): Unit = println("LiveDatabaseApi closed")
618
+ final class ConsoleLogger extends Logger {
619
+ def info(msg: String): Unit = println(msg)
504
620
  }
505
621
 
506
- object DatabaseApi {
507
- // Tell Scope how to build the trait by wiring a concrete implementation.
508
- given Wireable.Typed[Config, DatabaseApi] =
509
- Wireable.fromWire(shared[LiveDatabaseApi].shared.asInstanceOf[Wire[Config, DatabaseApi]])
622
+ final class App(logger: Logger) {
623
+ def run(): Unit = logger.info("Hello!")
510
624
  }
511
625
 
512
- final case class Config(url: String)
626
+ // Wire.shared[ConsoleLogger] satisfies the Logger dependency via subtyping:
627
+ val appResource: Resource[App] =
628
+ Resource.from[App](
629
+ Wire.shared[ConsoleLogger]
630
+ )
513
631
 
514
632
  Scope.global.scoped { scope =>
515
- val deps = Context(Config("jdbc:postgresql://localhost/db"))
516
-
517
- val db: DatabaseApi @@ scope.Tag =
518
- scope.allocate(shared[DatabaseApi].toResource(deps))
519
-
520
- val out: String =
521
- scope.$(db)(_.query("SELECT 1"))
522
-
523
- println(out)
633
+ import scope._
634
+ val app: $[App] = allocate(appResource)
635
+ scope.use(app)(_.run())
524
636
  }
525
637
  ```
526
638
 
527
- Practical guidance:
528
- - Prefer `Wireable.fromWire(...)` when you already have a `Wire` you trust.
529
- - Put `given Wireable[...]` / `implicit val wireable: Wireable[...]` in the companion of the trait being injected.
639
+ **Single instance for diamond patterns:**
640
+
641
+ ```scala
642
+ trait Service
643
+ class LiveService extends Service
644
+ class NeedsService(s: Service)
645
+ class NeedsLive(l: LiveService)
646
+ class App(a: NeedsService, b: NeedsLive)
647
+
648
+ // One LiveService instance satisfies both Service and LiveService dependencies:
649
+ val appResource = Resource.from[App](
650
+ Wire.shared[LiveService]
651
+ )
652
+ // count of LiveService instantiations: 1
653
+ ```
530
654
 
531
655
  ---
532
656
 
533
657
  ### Interop escape hatch: `leak`
534
658
 
535
- Sometimes you must hand a raw value to code that cannot work with `@@` types.
659
+ Sometimes you must hand a raw value to code that cannot work with `$[A]` types.
536
660
 
537
661
  ```scala
538
662
  import zio.blocks.scope._
539
663
 
540
664
  Scope.global.scoped { scope =>
541
- val db = scope.allocate(Resource(new Database))
665
+ import scope._
666
+ val db: $[Database] = allocate(Resource(new Database))
542
667
 
543
668
  val raw: Database = leak(db) // emits a compiler warning
544
669
  // thirdParty(raw)
@@ -549,6 +674,168 @@ Scope.global.scoped { scope =>
549
674
 
550
675
  ---
551
676
 
677
+ ## Common compile errors
678
+
679
+ The scope macros produce beautiful, actionable compile-time error messages with ASCII diagrams and helpful hints:
680
+
681
+ ### Not a class (Wire.shared/unique on trait or abstract class)
682
+
683
+ ```
684
+ ── Scope Error ──────────────────────────────────────────────────────────────
685
+
686
+ Cannot derive Wire for MyTrait: not a class.
687
+
688
+ Hint: Use Wire.Shared / Wire.Unique directly.
689
+
690
+ ────────────────────────────────────────────────────────────────────────────
691
+ ```
692
+
693
+ ### Unmakeable type (primitives, functions, collections)
694
+
695
+ ```
696
+ ── Scope Error ──────────────────────────────────────────────────────────────
697
+
698
+ Cannot auto-create String
699
+
700
+ This type (primitive, collection, or function) cannot be auto-created.
701
+
702
+ Required by:
703
+ ├── Config
704
+ └── App
705
+
706
+ Fix: Provide Wire(value) with the desired value:
707
+
708
+ Resource.from[...](
709
+ Wire(...), // provide a value for String
710
+ ...
711
+ )
712
+
713
+ ────────────────────────────────────────────────────────────────────────────
714
+ ```
715
+
716
+ ### Abstract type (trait or abstract class)
717
+
718
+ ```
719
+ ── Scope Error ──────────────────────────────────────────────────────────────
720
+
721
+ Cannot auto-create Logger
722
+
723
+ This type is abstract (trait or abstract class).
724
+
725
+ Required by:
726
+ └── App
727
+
728
+ Fix: Provide a wire for a concrete implementation:
729
+
730
+ Resource.from[...](
731
+ Wire.shared[ConcreteImpl], // provides Logger
732
+ ...
733
+ )
734
+
735
+ ────────────────────────────────────────────────────────────────────────────
736
+ ```
737
+
738
+ ### Duplicate providers (ambiguous wires)
739
+
740
+ ```
741
+ ── Scope Error ──────────────────────────────────────────────────────────────
742
+
743
+ Multiple providers for Service
744
+
745
+ Conflicting wires:
746
+ 1. LiveService
747
+ 2. TestService
748
+
749
+ Hint: Remove duplicate wires or use distinct wrapper types.
750
+
751
+ ────────────────────────────────────────────────────────────────────────────
752
+ ```
753
+
754
+ ### Dependency cycle
755
+
756
+ ```
757
+ ── Scope Error ──────────────────────────────────────────────────────────────
758
+
759
+ Dependency cycle detected
760
+
761
+ Cycle:
762
+ ┌───────────┐
763
+ │ ▼
764
+ A ──► B ──► C
765
+ ▲ │
766
+ └───────────┘
767
+
768
+ Break the cycle by:
769
+ • Introducing an interface/trait
770
+ • Using lazy initialization
771
+ • Restructuring dependencies
772
+
773
+ ────────────────────────────────────────────────────────────────────────────
774
+ ```
775
+
776
+ ### Subtype conflict (related dependency types)
777
+
778
+ ```
779
+ ── Scope Error ──────────────────────────────────────────────────────────────
780
+
781
+ Dependency type conflict in MyService
782
+
783
+ FileInputStream is a subtype of InputStream.
784
+
785
+ When both types are dependencies, Context cannot reliably distinguish
786
+ them. The more specific type may be retrieved when the more general
787
+ type is requested.
788
+
789
+ To fix this, wrap one or both types in a distinct wrapper:
790
+
791
+ case class WrappedInputStream(value: InputStream)
792
+ or
793
+ opaque type WrappedInputStream = InputStream
794
+
795
+ ────────────────────────────────────────────────────────────────────────────
796
+ ```
797
+
798
+ ### Duplicate parameter types in constructor
799
+
800
+ ```
801
+ ── Scope Error ──────────────────────────────────────────────────────────────
802
+
803
+ Constructor of App has multiple parameters of type String
804
+
805
+ Context is type-indexed and cannot supply distinct values for the same type.
806
+
807
+ Fix: Wrap one parameter in an opaque type to distinguish them:
808
+
809
+ opaque type FirstString = String
810
+ or
811
+ case class FirstString(value: String)
812
+
813
+ ────────────────────────────────────────────────────────────────────────────
814
+ ```
815
+
816
+ ### Leak warning
817
+
818
+ When using `leak(value)` to escape the scoped type system:
819
+
820
+ ```
821
+ ── Scope Warning ────────────────────────────────────────────────────────────
822
+
823
+ leak(db)
824
+ ^
825
+ |
826
+
827
+ Warning: db is being leaked from scope MyScope.
828
+ This may result in undefined behavior.
829
+
830
+ Hint:
831
+ If you know this data type is not resourceful, then add an Unscoped
832
+ instance for it so you do not need to leak it.
833
+
834
+ ────────────────────────────────────────────────────────────────────────────
835
+ ```
836
+
837
+ ---
838
+
552
839
  ## API reference (selected)
553
840
 
554
841
  ### `Scope`
@@ -556,24 +843,42 @@ Scope.global.scoped { scope =>
556
843
  Core methods (Scala 3 `using` vs Scala 2 `implicit` differs, but the shapes are the same):
557
844
 
558
845
  ```scala
559
- final class Scope[ParentTag, Tag0 <: ParentTag] {
560
- type Tag = Tag0
846
+ sealed abstract class Scope extends Finalizer {
847
+ type $[+A] // = A at runtime (zero-cost)
848
+ type Parent <: Scope
849
+ val parent: Parent
561
850
 
562
- def allocate[A](resource: Resource[A]): A @@ Tag
851
+ def allocate[A](resource: Resource[A]): $[A]
852
+ def allocate[A <: AutoCloseable](value: => A): $[A]
563
853
  def defer(f: => Unit): Unit
564
854
 
565
- def $[A, B, S](scoped: A @@ S)(f: A => B)(
566
- using ev: this.Tag <:< S,
567
- escape: ScopeEscape[B, S]
568
- ): escape.Out
855
+ // Apply function to scoped value, returns scoped result
856
+ def use[A, B](scoped: $[A])(f: A => B): $[B]
857
+
858
+ // Construct a scoped value from a raw value
859
+ def $[A](a: A): $[A]
860
+
861
+ // Lower parent-scoped value into this scope
862
+ def lower[A](value: parent.$[A]): $[A]
863
+
864
+ // Escape hatch: unwrap scoped value (emits compiler warning)
865
+ // Scala 3:
866
+ inline def leak[A](inline sa: $[A]): A // macro — emits warning
867
+ // Scala 2:
868
+ def leak[A](sa: $[A]): A // macro — emits warning
869
+
870
+ // Creates a child scope - requires Unscoped evidence on return type
871
+ // Scala 3:
872
+ def scoped[A](f: (child: Scope.Child[self.type]) => child.$[A])(using Unscoped[A]): A
873
+ // Scala 2 (macro rewrites the types; declared signature is untyped):
874
+ def scoped(f: Scope.Child[self.type] => Any): Any // macro
569
875
 
570
- def apply[A, S](scoped: Scoped[S, A])(
571
- using ev: this.Tag <:< S,
572
- escape: ScopeEscape[A, S]
573
- ): escape.Out
876
+ implicit class ScopedOps[A](sa: $[A]) {
877
+ def map[B](f: A => B): $[B]
878
+ def flatMap[B](f: A => $[B]): $[B]
879
+ }
574
880
 
575
- // Creates a child scope with an existential tag (fresh per call)
576
- def scoped[A](f: Scope[this.Tag, ? <: this.Tag] => A): A
881
+ implicit def wrapUnscoped[A: Unscoped](a: A): $[A]
577
882
  }
578
883
  ```
579
884
 
@@ -587,20 +892,17 @@ object Resource {
587
892
  def acquireRelease[A](acquire: => A)(release: A => Unit): Resource[A]
588
893
  def fromAutoCloseable[A <: AutoCloseable](thunk: => A): Resource[A]
589
894
 
590
- // Macro-derived constructors:
591
- def from[T]: Resource[T]
592
- def from[T](wires: Wire[?, ?]*): Resource[T]
593
-
594
- // Internal / produced by wires:
595
- def shared[A](f: Finalizer => A): Resource.Shared[A]
596
- def unique[A](f: Finalizer => A): Resource.Unique[A]
895
+ // Macro - DI entry point:
896
+ def from[T]: Resource[T] // zero-dep classes
897
+ def from[T](wires: Wire[?, ?]*): Resource[T] // with dependency wires
597
898
 
598
- final class Shared[+A] extends Resource[A]
599
- final class Unique[+A] extends Resource[A]
899
+ // Internal (used by generated code):
900
+ def shared[A](f: Finalizer => A): Resource[A]
901
+ def unique[A](f: Finalizer => A): Resource[A]
600
902
  }
601
903
  ```
602
904
 
603
- ### `Wire` and `Wireable`
905
+ ### `Wire`
604
906
 
605
907
  ```scala
606
908
  sealed trait Wire[-In, +Out] {
@@ -610,9 +912,16 @@ sealed trait Wire[-In, +Out] {
610
912
  def toResource(deps: zio.blocks.context.Context[In]): Resource[Out]
611
913
  }
612
914
 
613
- trait Wireable[+Out] {
614
- type In
615
- def wire: Wire[In, Out]
915
+ object Wire {
916
+ // Macro entry points:
917
+ def shared[T]: Wire.Shared[?, T] // derive from T's constructor
918
+ def unique[T]: Wire.Unique[?, T] // derive from T's constructor
919
+
920
+ // Wrap pre-existing value (auto-finalizes if AutoCloseable):
921
+ def apply[T](value: T): Wire.Shared[Any, T]
922
+
923
+ final case class Shared[-In, +Out] extends Wire[In, Out]
924
+ final case class Unique[-In, +Out] extends Wire[In, Out]
616
925
  }
617
926
  ```
618
927
 
@@ -620,8 +929,17 @@ trait Wireable[+Out] {
620
929
 
621
930
  ## Mental model recap
622
931
 
623
- - Use `Scope.global.scoped { scope => ... }` to create a safe region.
624
- - Allocate managed things with `scope.allocate(Resource(...))` (or `Resource.from[...]`).
625
- - Use scoped values only via `scope.$(value)(...)` or via `Scoped` computations executed by `scope(scoped)`.
626
- - Nest with `scope.scoped { child => ... }` to create a tighter lifetime boundary.
932
+ - Use `Scope.global.scoped { scope => import scope._; ... }` to create a safe region.
933
+ - For simple resources: `allocate(Resource(value))` or `allocate(Resource.acquireRelease(...)(...))`
934
+ - For dependency injection: `allocate(Resource.from[App](Wire(config), ...))` auto-wires concrete classes, you provide leaves and overrides.
935
+ - Use `scope.use(value)(f)` to work with scoped values all operations are eager.
936
+ - `$[A] = A` at runtime — zero-cost opaque type.
937
+ - The `scoped` method requires `Unscoped[A]` evidence on the return type.
938
+ - Use `lower(parentValue)` to access parent-scoped values in child scopes.
939
+ - Return `Unscoped` types from child scopes to extract raw values.
627
940
  - If it doesn't typecheck, it would have been unsafe at runtime.
941
+
942
+ **The 3 macro entry points:**
943
+ - `Wire.shared[T]` — shared wire from constructor
944
+ - `Wire.unique[T]` — unique wire from constructor
945
+ - `Resource.from[T](wires*)` — wire up T and all dependencies