stdmod
May 9, 2026 ยท View on GitHub
Redirects stdlib-shaped import paths through
modfsto the syntheticgithub.com/mvm-sh/stdmodule.
Overview
stdmod is the bridge between stdlib-style imports ("errors",
"slices", "encoding/hex") and the proxy-format module that mvm uses
to serve them. It exists so that the parser sees one uniform pipeline
for all interpreted source -- whether the bytes come from the embedded
zip baked into the binary or from a network fetch through proxy.golang.org.
It sits between interp/main and modfs:
parser import "errors"
|
v
stdmod.redirectFS --rewrite--> github.com/mvm-sh/std/errors
|
v
modfs.FS --serves from--> in-memory module (injected zip or proxy fetch)
See ADR-017 for the design rationale.
Key types and functions
ModulePath("github.com/mvm-sh/std") andVersion("v0.1.0") -- compile-time pins. The embeddedstdlib/src.zipmust match these somodfs.Injectkeys the in-memory module under the same path.Resolve() (modPath, version string)-- returns(ModulePath, Version)unless theMVMSTDenv var overrides them. Format:MVMSTD=<modpath>@<version>(e.g.github.com/myfork/std@v0.2.1).IsStdlibImport(path string) bool-- true whenpath's first segment contains no dot. Matchescmd/go's stdlib-vs-module predicate.FS(backing *modfs.FS) fs.FS-- wrap an existing*modfs.FSin the redirectingfs.FS. Stdlib-shaped lookups are rewritten to<modPath>/<path>and delegated tobacking; everything else returnsfs.ErrNotExistso the parser falls through to its next FS in the chain.DefaultFS() fs.FS-- returns a redirect FS over an offline*modfs.FSpre-populated with the embedded std zip. Memoized viasync.OnceValue: every caller shares one parsed module so the zip walk runs at most once per process. Used byinterp.NewInterpreterfor the offline-default code path.
Internal design
Two construction paths, one redirect
flowchart LR
NI["interp.NewInterpreter()"] --> DF["stdmod.DefaultFS()"]
DF -->|once| OFFLINE["offline modfs<br/>+ embedded zip injected"]
OFFLINE --> R1["redirectFS"]
MAIN["main.wireFS(i)"] --> BUILD["buildModFS()<br/>(GOPROXY-aware)"]
BUILD --> NETWORK["network modfs<br/>+ embedded zip injected"]
NETWORK --> R2["redirectFS"]
R1 --> P["parser stdlibFS slot"]
R2 -. overrides .-> P
NewInterpreter installs the default offline-only FS so embedders and
tests have a working stdlib without ceremony. main's wireFS builds
a network-capable modfs for CLI invocations and overrides the slot,
because that modfs must also be reused as the parser's remoteFS to
share one cache between stdlib and third-party imports.
Redirect logic
redirectFS.rewrite(name) returns <modPath>/<name> when
IsStdlibImport(name) is true, otherwise the empty string and false.
Open, Stat, and ReadDir each call rewrite and either delegate
to the backing modfs or return a fs.PathError with the appropriate
Op string. The redirect happens at the fs.FS boundary so neither
the parser nor modfs itself needs to know stdlib paths get treated
specially.
Override via MVMSTD
The env var format is <modpath>@<version>. Resolve does a
LastIndex("@") split (last to allow @ characters earlier in the
path, e.g. a hypothetical gopkg.in style). If no @ is present the
whole string is taken as the modpath and Version is reused.
When MVMSTD overrides to a different module path or version, the
embedded zip (keyed under the compile-time (ModulePath, Version)) is
no longer the active key -- modfs falls through to the network on the
first lookup. The embedded bytes stay present in the binary but inert
for the override session.
Dependencies
modfs/-- the in-memory FS the redirect delegates to.stdlib/-- consumesEmbeddedStd()for the offline floor.
Open questions / TODOs
- Embedded-zip fallback under override. When
MVMSTDswaps togithub.com/myfork/std@v0.X.Y, an offline user cannot resolve the override without first fetching it once. A "try the override first, fall back to the embedded if offline and the override matches the baked path" rule would be confusing; current behavior (override means override) is simpler. Worth revisiting only if a real use case arises. - Compile-time pin sync.
stdmod.Versionand the version label baked intostdlib/src.zip(instdlib/gen_stdzip.go) are bumped by hand after each std-repo retag. Ago generatecross-check (e.g. fail loudly when the constants disagree) would prevent silent cache-miss-then-fetch behavior.