Skip to content

Resources: use and resource { }

Inside effect blocks, use binds an effect whose requirements include Scope, registering its release into the enclosing block’s scope, including release on interruption. The block defines the scope: a block containing use wraps itself in Effect.scoped, and every registered finalizer runs on success, failure, or interruption, LIFO:

report.hemhem
export let report = (userId: string) => effect {
  use conn = Db.acquire
  let rows = conn.query(sql)!
  render(rows)
}
report.ts — generatedts
export const report = (userId: string) =>
  Effect.scoped(
    Effect.gen(function* () {
      const conn = yield* Db.acquire;
      const rows = yield* conn.query(sql);
      return render(rows);
    }),
  );

To narrow a resource’s lifetime, use a smaller nested block:

effect {
  let rows = effect {
    use conn = pool.acquire
    conn.query(sql)!
  }!                      // conn released here
  transform(rows)         // runs after release
}

Use resource { } when the caller should own the lifetime. It has the same rules without the automatic scope wrapper: Scope stays in its requirements and acquisitions register into the caller’s scope. In the supported transport, Layer.effect consumes this scoped construction; there is no separate Layer.scoped:

let UsersLive = Layer.effect(Users, resource {
  use pool = Pg.makePool(cfg)
  makeUsersService(pool)
})

Acquisition is always written with use: the block type determines the lifetime. Cleanup without an acquired value uses defer:

let serve = effect {
  defer Effect.sync(() => instruction.clear(id))
  work()!
}

defer accepts an existing infallible Effect<void> cleanup. Lift synchronous work with Effect.sync, explicitly discard any returned value, and handle typed cleanup failures before registering them. Cleanups run LIFO on success, typed failure, defect, and interruption. In effect {} the block owns and closes the scope; in resource {} the caller owns it. Exit-sensitive finalizers remain explicit Effect.addFinalizer((exit) => ...) calls. Binding a Scope-carrying effect with plain ! is an error pointing at use. Finalizers run before catch handlers, so recovery never sees a live resource.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close