Practical quick reference for the V compiler, standard library, and tools. Written for AI coding agents; useful for humans too.
$if)Get operational from the repo root in three steps:
./v is missing): make./v -g -keepc -o ./vnew cmd/v./vnew for everything:./vnew run examples/hello_world.v./vnew -silent test vlib/v/./vnew fmt -w path/to/file.vThen read Top Rules and Agent Rules before making changes../v only to build ./vnew; use ./vnew for everything else../vnew and before the
subcommand/file, e.g. ./vnew -g run file.v
(not ./vnew run file.v -g); flags after the subcommand are passed
to that subcommand../vnew after compiler or core module changes
(see Build & Rebuild).All commands assume the repo root as the working directory. The default
location is /opt/v, but this may differ in your environment. If a
command fails due to missing paths, verify with pwd and adjust
accordingly. Use a per-command workdir only when a task requires a
subdir.
ci/ or Dockerfile* still require an explicit ask.fmt, targeted tests, or check-md.thirdparty/ unless explicitly requested. If changes
are needed there, ask for approval before proceeding.cmd/, vlib/,
doc/, examples/).
Exception: docs-only changes across many files are OK without asking;
call them out in the summary.ci/ or Dockerfile* unless explicitly
requested. If changes are needed there, confirm whether local
validation is expected or if CI-only coverage is acceptable../vnew fmt -w, add doc comments for
any public functions, run ./vnew check-md for markdown files, and
keep Markdown lines <= 100 chars. Add or update tests when introducing
a new public API.CONTRIBUTING.md and TESTS.md../v binary../v self without -o../v -o ./vnew cmd/v, then use ./vnew for all checks.-keepc.git stashmakegit stash apply
Check git status before stashing to avoid hiding unrelated work.
Do not stash unless explicitly instructed or the compiler is bricked.The repo docs use v in examples. In this environment:
./v -g -keepc -o ./vnew cmd/v instead of v self../vnew for all builds, runs, and tests.TESTS.md suggests v test-all before PRs; ask before running
./vnew test-all.
These overrides exist to keep agent workflows reproducible and to avoid
breaking the bootstrap compiler.CONTRIBUTING.md.Use this table to pick the minimum rebuild/tests quickly. See Build &
Rebuild and Testing for full details and edge cases.
Commands omit ./vnew for brevity; assume the ./vnew prefix.
This table is the minimum set; check Testing for additional triggers.
REPL and backend changes have additional triggers in Testing.
Note: cmd/v/ is compiler scope; treat changes there as compiler
changes.
When in doubt, ask before proceeding.
If you read only one section for tests, read Testing.
| Change area | Rebuild? | Minimum tests to run |
| --- | --- | --- |
| Docs only (.md) | No | check-md file.md |
| Compiler (vlib/v/, cmd/v/) | Yes | -silent vlib/v/compiler_errors_test.v; test vlib/v/ |
| Core modules (builtin/strings/os/strconv/time) | Yes | Smallest relevant tests |
| Parser-only (vlib/v/parser/) | Yes | test vlib/v/parser/ |
| Checker-only (vlib/v/checker/) | Yes | test vlib/v/checker/ |
| Comptime (vlib/v/comptime/) | Yes | test vlib/v/tests/; comptime-related tests |
| vlib (non-compiler) | No | Nearest *_test.v or test vlib/path/ |
| Tools (cmd/tools/) | No | Tool-specific test; else nearest *_test.v |
| Diagnostic/output changes | Yes | vlib/v/slow_tests/inout/compiler_test.v |
| C codegen (vlib/v/gen/c/) | Yes | vlib/v/gen/c/coutput_test.v |
git status; ensure ./vnew exists; rebuild if needed.
If ./vnew is missing, see Quick Start or Build & Rebuild../vnew with
./v -g -keepc -o ./vnew cmd/v (see Build & Rebuild)..v/.vsh files and run ./vnew check-md on
touched markdown.doc/, tutorials/) and note it.
For example: README.md for top-level CLI usage, doc/ for
compiler/tool docs, tutorials/ for learning material.cmd/tools
and stdlib CLI tools), or output formats.CHANGELOG.md or ROADMAP.md only when explicitly requested../vnew check-md file.md; no other tests
required unless a test explicitly reads those docs.
No rebuild is needed unless compiler or core modules changed.check-md in the summary..out files, state the rationale in the summary..out files unless a behavior change is intended;
otherwise treat mismatches as regressions.Behavior change: none or
Behavior change: fixed X.git status to confirm touched files before reporting.make, git, and a C compiler (clang or gcc).makev.bat for the initial build../v is missing): make
(Windows: makev.bat)../vnew (debug-friendly, recommended for agent workflows):
./v -g -keepc -o ./vnew cmd/v./v self directly; only build ./vnew with the commands
above../v is missing, run make first, then build ./vnew../vnew is missing but ./v exists, run ./v -o ./vnew cmd/v.vlib/v/ or cmd/v/.builtin, strings, os, strconv, time../v exists but compiler sources changed, still rebuild ./vnew
before tests../vnew.-g debug info (V line numbers).-cg debug info (C line numbers); often combined with -keepc.-keepc keep generated C file(s).-prod optimized build.-o file output path/name.-cc clang pick a C compiler.-cstrict be stricter about the generated C.-b js|native|wasm select a backend.-os <os> cross-compile target selection (when supported).// the_name does ...unsafe{ code } blocks where possible, and minimize
their scope. unsafe is acceptable for low-level interop (e.g. C
pointer casts, manual memory management) but should never wrap
ordinary V logic. When unsafe is required, keep the block as small
as possible and add a comment explaining why it is necessary..vv files in vlib/v/slow_tests/inout/ as fixtures; avoid
formatting unless a behavior change is intended and output
expectations are updated.module line). Mismatches cause silent import failures.import abc.def).C. or JS. symbols in plain .v files..c.v / .js.v files.-Wimpure-v to catch accidental impurity.C./JS. uses in .v files unless
required. If you must change those lines, prefer moving the interop
code to .c.v/.js.v.const_ prefixes in C function
redeclarations when needed (helps with -cstrict and C static
analysis tooling).$if)V supports environment-specific file suffixes. Prefer them when the whole file is platform/backend specific.
Common patterns:
*.c.v (C backend), *.js.v (JS backend), *.native.v (native
backend), *.wasm.v (WASM backend)*_windows.c.v, *_linux.c.v, *_nix.c.v, with *_default.c.v
as fallback*_d_customflag.v is included only with -d customflag*_notd_customflag.v is included only when that flag is NOT passedNotes:_d_flag_linux.c.v; use
_d_flag.v plus $if linux {} inside the file.$if blocks:$if windows { ... } $else { ... }$if reference.V uses $ as a prefix for compile-time (comptime) operations. These
are evaluated by the compiler, not at runtime. AI agents frequently
confuse comptime and runtime constructs; this section clarifies the
boundaries.
$if$if evaluates conditions at compile time. It is not a runtime if.
Use it for platform, compiler, and custom-flag checks:
$if windows { ... } $else $if linux { ... } $else { ... }$if debug { ... } (enabled by -g or -cg)$if prod { ... } (enabled by -prod)$if custom_flag ? { ... } (enabled by -d custom_flag)Full list of builtin $if options: see the table at
https://docs.vlang.io/conditional-compilation.html.Common mistakes:if where $if is needed for platform-specific code.
Runtime if will fail to compile if it references platform-specific
symbols; $if excludes the block entirely on non-matching platforms.? suffix for custom flags: $if myflag ? { ... }.
Without ?, the compiler treats it as a builtin option and silently
ignores it.$for$for iterates over type metadata at compile time. It works with:
StructType.fields - iterate struct fieldsStructType.methods - iterate struct methodsEnumType.values - iterate enum valuesStructType.attributes - iterate struct attributesSumType.variants - iterate sum type variantsmethod.params - iterate method parametersInside $for blocks, use $if to branch on field/method types:fn main() {
$for field in MyStruct.fields {
$if field.typ is string {
println(field.name)
}
}
}
Also works with generics: T.fields, param.fields where
fn gen[T](param T) {}.Common mistakes:for to iterate struct fields. This does not work;
V has no runtime reflection. Always use $for.obj.$method() outside a $for m in Type.methods block.
The $method() call is only valid inside a comptime method
iteration.$for produces a runtime loop. It does not; the
compiler unrolls it into concrete code for each field/method/variant.Only the following $-prefixed functions are supported:
$embed_file('path') - embed a file's contents into the binary.
Paths can be absolute, relative to the source file, or use pseudo
variables like @VEXEROOT or @VMODROOT.$tmpl('path') - compile a V template file (used by veb).$env('VAR') - read an environment variable at compile time.$d('ident', default) - read a -d ident=value compile-time
define, with a default fallback.$res('path') - embed a resource (Android).$compile_error('msg') - emit a compile-time error.$compile_warn('msg') - emit a compile-time warning.$pkgconfig('name') - query pkg-config at compile time.Common mistakes:$typeof,
$sizeof as comptime calls). Use typeof(expr).name and
sizeof(Type) instead; these are builtins, not $-prefixed.$embed_file returns a string. It returns an
EmbedFileData object; use .to_string() or .to_bytes().$env where a runtime os.getenv is appropriate, or vice
versa. $env is baked in at compile time and cannot change at
runtime.These are @-prefixed identifiers substituted at compile time:
@FN - current function name.@METHOD - ReceiverType.MethodName.@MOD - current module name.@STRUCT - current struct name.@FILE, @DIR, @LINE, @COLUMN, @FILE_LINE - source
location.@LOCATION - file, line, and current type+method; good for logging.@VEXE, @VEXEROOT - path to the V compiler and its directory.@VHASH, @VCURRENTHASH - compiler commit hashes.@VMOD_FILE, @VMODHASH, @VMODROOT - nearest v.mod info.@BUILD_DATE, @BUILD_TIME, @BUILD_TIMESTAMP - build time (UTC).
Override with the SOURCE_DATE_EPOCH env var for reproducible
builds.@OS, @CCOMPILER, @BACKEND, @PLATFORM - build environment.Comptime type groups combine multiple types into a higher-level type
for use in generic or comptime $if checks:
$int - all integer types$float - all float types$array - all array types$map - all map types$struct - all struct types$enum - all enum types$alias - all type aliases$sumtype - all sum types$function - all function types$interface - all interface types$option - all option typesExample: $if field.typ is $int { ... }Comptime logic lives in vlib/v/comptime/ and is exercised by the
checker, parser, and cgen stages. Changes here require a rebuild of
./vnew and should be tested with ./vnew -silent test vlib/v/tests/ plus
any comptime-specific tests. See the decision table and Testing.
./vnew run file.v../vnew file.v (creates executable)../vnew -g run file.v../vnew -keepc -cg run file.v../vnew run examples/hello_world.v.Run:
./vnew path/to/file_test.v../vnew test path/to/file_test.v../vnew -silent test path/to/dir/../vnew -stats test path/to/dir/../vnew -silent vlib/v/compiler_errors_test.v.VAUTOFIX=1 ./vnew -silent vlib/v/compiler_errors_test.v../vnew test-all.
Ask before running ./vnew test-all unless explicitly requested.When:vlib/v/ or cmd/v/):
Run ./vnew -silent vlib/v/compiler_errors_test.v,
./vnew -silent test vlib/v/.vlib/v/parser/):
Run ./vnew -silent test vlib/v/parser/.vlib/v/checker):
Run ./vnew -silent test vlib/v/checker/.*_test.v or
./vnew -silent test vlib/path/.cmd/tools/): Run tool-specific tests. If none exist,
run the smallest relevant *_test.v that exercises the tool.
Note: cmd/v/ is compiler scope, not tools.
Examples: cmd/tools/vfmt -> vlib/v/fmt/fmt_test.v .
cmd/tools/vdoc -> cmd/tools/vdoc/vdoc_test.v../vnew -silent vlib/v/slow_tests/inout/compiler_test.v../vnew -silent vlib/v/gen/c/coutput_test.v.
Consider a stricter validation pass:
./vnew -cstrict -cc clang -silent test vlib/v/../vnew -silent vlib/v/slow_tests/repl/repl_test.v../vnew -silent test-all.-b <backend> test vlib/ runs. Prefer targeted *_test.v
files or small test dirs with -b js|native|wasm.If time-constrained, prioritize
./vnew -silent vlib/v/compiler_errors_test.v and the smallest targeted tests.
Run vlib/v/slow_tests/inout/compiler_test.v
and vlib/v/gen/c/coutput_test.v when output or codegen changes are
likely.
See TESTS.md for more guidance on test selection and output
expectations.
See CONTRIBUTING.md for broader workflow guidance.Concrete triggers:vlib/v/slow_tests/inout/compiler_test.v when error text or output
formatting changes, or changes in checker/parser error reporting.vlib/v/gen/c/coutput_test.v for changes under vlib/v/gen/c/ or
C codegen output paths.Types:*_test.v files with test_ functions..vv source + .out expected output in
vlib/v/slow_tests/inout/.
Example: ./vnew -silent vlib/v/slow_tests/inout/compiler_test.v.vlib/v/tests/** may use .run.out expectations; run with
./vnew -silent test vlib/v/tests.Docs-only guidance: see Reporting.
If time-boxed, run at least the smallest relevant test and note
skipped coverage in the summary.VAUTOFIX=1 - Auto-update .out files when tests fail (run twice).
Use only when a behavior change is intended.VTEST_ONLY=glob_pattern - Run only tests matching pattern.VTEST_HIDE_OK=1 - Hide successful tests, show only failures../vnew -silent test path/to/dir/ - Show only failed tests (if any), and a summary report.-cc tcc can speed test builds when TCC is available..out files only when behavior changes
are intended; note the rationale in the summary.-showcc prints the C compile command.-show-c-output prints the C compiler output../vnew -o ./w -d trace_checker cmd/v./w file.v-keepc -cg is the common combo.-printfn <name> -o file.c emits only the named C function to standart
output. The name uses the modulename__fnname format (e.g.
main__main). This flag can be repeated to print multiple
functions. Methods/generics may use more complex C names; use
-keepc to confirm exact symbols.-d trace_scanner|trace_parser|trace_checker|trace_gen.
These flags can help diagnose a problem when a stage stops earlier
than expected.-d time_parsing|time_checking.-keepc -g.-keepc -cg -cc clang.-b js|native|wasm and -show-c-output where applicable; avoid
broad -b <backend> test vlib/./*tom51*/) in the compiler
source. Rebuild and generate C code, then search the generated file for these
tags to quickly map generated C code back to the exact compiler source
location. This is especially useful when multiple code paths generate
similar-looking output and you need to identify which path is actually used.
Example:// In vlib/v/gen/c/assign.v:
g.write('builtin___option_ok/*tom51*/(&(${styp}[]) { ')
// After rebuild, search generated C code for "tom51":
// builtin___option_ok/*tom51*/(&(int[]) { ... });
Remember to remove these debug tags after fixing the issue.The V compiler has the following stages, orchestrated by the
v.builder module:
v.scanner -> v.parser -> v.checker -> v.transformer ->
v.markused -> v.gen.c
Their corresponding folders are: vlib/v/scanner, vlib/v/parser,
vlib/v/checker, vlib/v/transformer, vlib/v/markused, vlib/v/gen/c .
There are additional subsystems (supporting or optional compiler
modules) like v.comptime, v.generics, v.pref, v.reflection,
v.callgraph, etc.
vlib/: Standard library (changes here can affect the compiler
itself).cmd/v/v.v: Compiler entry.cmd/tools/: vfmt, vdoc, vup, vquest, etc.examples/: Example programs.thirdparty/: Bundled C libraries (tcc, mbedtls, sokol, etc.).vlib/v/: Compiler modules.vlib/v/tests/: Compiler feature tests (including comptime).vlib/v/slow_tests/: Output-matching and slow tests for the
compiler.vlib/v/slow_tests/inout/ - Output comparison tests
(.vv + .out pairs)vlib/v/parser/ - Parser error testsvlib/v/checker/ - Checker error testsvlib/v/gen/c/testdata/ - C codegen tests (.vv + .c.must_have)examples/compiletime/ - Comptime usage examples (including
reflection.v)c.error('message', pos) - hard error, stops compilation.c.warn('message', pos) - warning, allows compilation.c.note('message', pos) - informational only.fn (mut c Checker) receiver.vlib/v/checker/errors.v.?Type (optional, can be none) vs !Type (result, can
error).vlib/v/tests/.vlib/v/tests/ for option/result test files../vnew fmt -w <file> for touched .v and .vsh files.
Format only touched files unless explicitly asked to reformat broader
scope../vnew -silent test-fmt.
Run only when asked or when validating the full tree../vnew check-md file.md for touched .md files
(required before commits)../vnew vet vlib/v
Run only when asked or when making broad checker changes (more than
3 files in vlib/v/checker/)../vnew doc -readme -all -l module_name.rg pattern (or git grep); list files: rg --files../vnew git-fmt-hook install.CONTRIBUTING.md for full commit message conventions and PR
workflow.git rebase over git merge for a linear history, but always ask
if the situation is ambiguous.VFLAGS='-g' ./vnew test-all)..tmp.c files are written (V uses
TMPDIR/v/).@BUILD_DATE, @BUILD_TIME, @BUILD_TIMESTAMP).V2CC, V2CFLAGS, V2VERBOSE (for v2 development).builtin, strings, os, strconv, time) can
affect the compiler because it is a V program. Rebuild ./vnew when
they change.os.execute('v ...') in their
source. The ./vnew workflow does not protect against these; if you
encounter unexpected behavior from such calls, check whether the
code is invoking the system v instead of ./vnew and adjust
accordingly../vnew can cause confusing failures; rebuild if behavior
seems off.fmt and check-md.-keepc
(creates /tmp/*.tmp.c; see also TMPDIR in Environment Variables).$if, $for, $embed_file, etc.) is a common
source of AI mistakes. See Compile-Time Code and Reflection for
correct usage and pitfalls.CONTRIBUTING.md and TESTS.md for:.out updates.