use and wire them at entry points with with. Request data stays separate from shared infrastructure.
Two primitives
Parameters carry data (order IDs, request bodies). Providers carry services the host supplies (logger, clock, repo). Keeping them separate clarifies what flows through your API boundary and what is ambient context.
Declare dependencies with use
Contracts are ordinary types: shapes with methods, or imported Go interfaces:
- Forst
- Generated Go
use sites into Providers(f) for each function and propagates requirements transitively through calls. You can see the obligation chain in diagnostics and LSP hovers: which services a handler needs, and which entry point must supply them.
Wire at the boundary
Entry points satisfy every Provider the call graph needs using awith { … } { … } block. Production wiring and test wiring use the same mechanism:
- Forst
- Generated Go
Logger, Clock). Values must structurally satisfy the contract: plain structs with matching methods, or Go types that implement an imported interface.
Void contract methods
Contract methods without a return type (such asinfo(msg String)) must stay void on implementations. A trailing fmt.Println(...) in the method body can infer a non-void Result return from Go’s (int, error) signature; the compiler reports that mismatch. Use println for side-effect logging in void provider methods, or declare an explicit return type if you intend to return the call result. See Return inference and implicit returns.
Shadowing
Nestedwith blocks merge with outer wiring. When the inner block supplies the same contract key, it shadows the outer value for that scope; other keys still forward from outside:
- Forst
- Generated Go
Tests
Tests reuse the samewith blocks to swap fakes without a separate DI framework:
- Forst
- Generated Go
Compared to other approaches
The Providers RFC surveyed these patterns before settling onuse and with. See the Providers RFC hub for the full prior art writeup.
Forst treats
context.Context separately from Providers. Declare services with use so requirements stay typed and visible. Use context for cancellation and request scoped values. Use with blocks for service wiring. Sidecar and TypeScript exports stay data only. The host wires Providers in Go before exposing a handler.
What gets emitted
Providers lower to a deduped struct parameter at the Go boundary. Production and test wiring compile to ordinary struct literals: readable Go, no runtime container.Caveats
Providers are experimental. The RFC surface is still evolving. Pin your compiler version and runtask example:providers when upgrading.
Host wiring only
Providers lower in Go. They are not exported to TypeScript or sidecar invoke payloads. Functions with unsatisfiedProviders(f) are omitted from generated clients. Wire with blocks in Go main before exposing handlers.
See Call Forst from Node § Caveats and Generate client types § Caveats.
Void contract methods
A trailingfmt.Println(...) in a void provider method can infer a non-void Result return from Go’s (int, error) signature. Use println for side effect logging, or declare an explicit return type. See Return inference and implicit returns.
context.Context is separate
Providers are typed services declared with use. Cancellation and request scoped values stay in context, not in Provider wiring.
Try it
From the Forst repository:Related
Go interop
Import Go interfaces as Provider contracts.
Editor workflow
LSP hovers show Provider obligations and related diagnostics.