use and supply them at entry points using with. This way services don’t need to be threaded through every parameter list, and tests can swap in fakes or mocks without changing the function under test.
Two primitives
Parameters continue carrying highly dynamic data like request bodies. Providers carry services that typically rarely change (e.g. loggers, clocks and repositories). 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.
- Do not end a void method with a Go call that returns values, such as
fmt.Println(...). With no declared return type, that trailing call becomes an implicit return, so the method no longer matches the void contract. - Prefer Forst
println/printwhen you only need a side effect, or declare an explicit return type when you intend to return the call result.
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
We surveyed these patterns before settling onuse and with. See the Providers RFC 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 check the Providers examples 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 over HTTP § Caveats and Generate client types § Caveats.
Void contract methods
- A trailing Go multi-return call such as
fmt.Println(...)becomes an implicit return and breaks a void contract method. - Prefer Forst
println/printfor side effects, or declare an explicit return type.
context.Context is separate
Providers do not replace Go’s context.Context. They solve a different problem.
context.Contextremains the right place for cancellation, deadlines, and request-scoped values such as a request ID or trace span. Keep passingctxas a normal parameter when a function needs those signals.- Providers are for stable typed services declared with
use: loggers, clocks, repositories, and similar host-supplied dependencies. The compiler tracks who must supply them at entry points. - Avoid stuffing those services into
context.WithValue. That pattern is untyped, easy to miss at call sites, and has no compile-time obligation chain. Providers exist so you do not need to overloadcontextfor DI.
context for the request lifecycle, and Providers for the services the host supplies once and reuses across calls.
Examples
See the provider examples on GitHub Deep dive: Providers RFC hubRelated
Go interop
Import Go interfaces as Provider contracts.
Editor workflow
LSP hovers show Provider obligations and related diagnostics.