Tools
v fmt
You don't need to worry about formatting your code or setting style guidelines.
v fmt takes care of that:
v fmt file.v
It's recommended to set up your editor, so that v fmt -w runs on every save.
A vfmt run is usually pretty cheap (takes <30ms).
Always run v fmt -w file.v before pushing your code.
The formatter checks syntax without requiring the code to pass semantic checks. For example, it preserves closure captures and loop binder mutability while you edit incomplete code.
Backend options before fmt, such as v -b arm64 fmt file.v, or in VFLAGS are honored.
The arm64 and eval backends use the same source formatting rules as c.
A function, loop, if branch or match branch whose body is a single statement
stays on one line when you write it that way and it fits in 100 columns:
A comment after a compact match branch's closing brace stays with that branch,
including the final else branch.
During the formatter transition, v fmt -verify and v fmt -c accept
files matching either current or legacy vfmt output. v fmt -w uses current formatting,
so it may rewrite a file accepted by either check mode.
Disabling the formatting locally
To disable formatting for a block of code, wrap it with // vfmt off and
// vfmt on comments.
// Not affected by fmt
// vfmt off
... your code here ...
// vfmt on
// Affected by fmt
... your code here ...
v mcp
v mcp runs a Model Context Protocol server
that gives a coding agent the V compiler's own view of a project: what the code
declares, what does not compile, what a symbol refers to, and the bundled skills
that describe the language.
v mcp serve # over stdio, which is what an MCP client launches
v mcp serve --http 127.0.0.1:0 # over Streamable HTTP
v mcp serve --root DIR # resolve relative paths against DIR
v mcp serve --read-only # register no tool that writes a file
v mcp tools # list the tools, with what each one answers
The server is not a separate index. It calls the V parser, checker and formatter in process, so it answers correctly about code that does not compile yet. Only the tools that genuinely compile or run something start the compiler.
The tools fall into groups:
- Project:
v_project_info,v_modules,v_files. - Code:
v_ast,v_symbols,v_symbol_at,v_references,v_stdlib_doc. - Checking:
v_check,v_test_run,v_doctor,v_veb_routes,v_skills. - Running:
v_run,v_eval. - Editing:
v_edit_replace,v_rename_symbol,v_format.
The editing tools default to reporting a plan rather than writing:
v_rename_symbol and v_format are dry runs unless told otherwise, and
v_edit_replace requires the caller to pass back the text it expects to find, so
it refuses to write over a concurrent change. --read-only does not register them
at all.
Symbol renames include named struct initializer keys and preserve the @ prefix
on escaped method calls. Formatting refuses source with parser errors and preserves
the original file.
To use it from an MCP client, point the client at the command:
{
"mcpServers": {
"v": { "command": "v", "args": ["mcp", "serve"] }
}
}
The server also publishes its model instructions, which describe the order to call
the tools in. Print them with v mcp serve --instructions.
v skills
v skills installs the agent skills that ship with the compiler. A skill is a
directory with a SKILL.md entry point, the layout coding agents already read.
v skills list # the bundled catalog, and where each one stands
v skills add v-mcp # install into .agents/skills/ of this project
v skills add v-mcp --global # install into ~/.agents/skills for this user
v skills remove v-mcp # uninstall
v skills path v-mcp # where a skill is installed
v skills update # refresh unchanged installs from newer bundles
A project install is committed and shared with the team; a --global install
applies to every project on the machine. Installing a skill that is already there
is skipped rather than overwritten, so a local edit survives; --force restores
the bundled copy. v skills list marks unchanged installations from older bundles
as stale, and locally edited or unrecorded installations as edited or unrecorded.
Each status names the update command for that scope; global installations need
v skills update --global. A deleted installed file also counts as a local edit.
v skills update uses the recorded installation digest to refresh unchanged skills
from newer bundles. Locally edited or unrecorded installations are held back unless
--force is passed; unreadable files are treated as unknown and held back too.
Use --dry-run to preview updates. The provenance file origin.json must be a regular
file: installation refuses symlinks and other file types before replacing skill content.
v.skills.content_digest returns an error if any requested file cannot be read.
Installation stages all bundled files in a sibling directory before replacing an
existing skill. A staging failure preserves the installed copy; a failed replacement
attempt restores the previous directory. Existing files at the skill destination are
refused, including with --force.
The v.skills.refresh_candidates API returns separate lists of safe refreshes
and installations held back for local changes or missing provenance. The
out_of_date API only compares content and cannot distinguish these cases.
Skill names must contain only lowercase letters, digits and single hyphens, and
must match the bundled name. Installation stays within an immediate child of the
skills directory, including with --force; bundled file paths cannot escape that
skill and must refer to regular files. The v-workflow check script exits with a
nonzero status when any compilation, formatting or vet check fails. The bundled
v-testing runner uses the normal child reporter while preserving other VFLAGS
options, and treats an empty test selection as a failure.
The skills are read from the source tree at run time rather than embedded into the binary, so a skill can be reviewed and diffed in the repository and adding one needs no rebuild.
v clean
A V build writes its executable next to the sources it was built from, named
after them. That is convenient for v run file.v and awkward for a project you
have been building in place, because the binaries pile up beside the code and
nothing else in the toolchain tracks them.
v clean removes the executables a default build would produce for the paths
you name:
v clean # the current directory
v clean ./cmd/mytool # one project folder
v clean hello.v # one source file
For a directory the executable is named after the directory, and for a .v
file after the file, which is exactly what the compiler does, so v clean .
after a v . build removes the binary that build left:
$ v clean .
removed `/home/me/mytool/mytool`
Anything else in the directory is left alone. v clean removes the one name
the compiler itself would have written and nothing more, so a stray executable
or a source file survives it.
Use -n to see what it would do first, and -x to have it print each removal
as a command:
$ v clean -n .
rm /home/me/mytool/mytool
A path whose executable name cannot be worked out with certainty is refused instead of guessed, and the command exits 1, which matters because this command deletes files.
v clean does not touch the build cache. That is v wipe-cache, kept separate
because it affects every project on the machine rather than the ones named
here.
v env
v env prints the environment variables that steer the V compiler and its
tools. Every setting is reported on its own NAME="value" line, which makes
the output easy to read in a script:
v env
VEXE="/home/me/v/v"
VROOT="/home/me/v"
VOS="linux"
VARCH="amd64"
VVERSION="V 0.5.2 8e2b0f4c1a"
VMODULES="/home/me/.vmodules"
VTMP="/tmp/v_1000"
VFLAGS=""
CFLAGS=""
LDFLAGS=""
...
A variable that is not set reports the value V would use anyway, so VMODULES
and VTMP show their default paths instead of an empty string. That makes
v env the place to look when a build picks up a setting you did not expect,
or when you want to know which folder V writes temporary files to.
Ask for one setting to get just its value, with no quoting and no other lines, which is what a shell substitution wants:
# install a module without hardcoding where that is
v install --path "$(v env VMODULES)"
Use -json to get the same values as a JSON object:
v env -json
v env NAME fails with an error naming the known settings if the name is not
one of them. For a bug report, use v doctor instead: it also shows compiler
versions, git state and C toolchain details.
Note that VOSARGS replaces the whole command line of every v invocation, so
exporting it in a shell makes each later v call ignore its own arguments.
v shader
You can use GPU shaders with V graphical apps. You write your shaders in an
annotated GLSL dialect
and use v shader to compile them for all supported target platforms.
v shader /path/to/project/dir/or/file.v
Currently you need to include a header and declare a glue function before using the shader in your code.
v tool
VPM installs modules rather than binaries, so a CLI tool written in V has to be
run by path today: v run ~/.vmodules/mytool, or v run ../mytool for a checkout
beside the project. Both need a path you have to know and keep correct.
v tool NAME resolves NAME the way an import would, then builds and runs that module:
$ v tool greet
hello from the tool
Because it reuses the compiler's own module lookup, that finds a tool installed
with v install and equally a tool you have a checkout of next to the project,
without either path being written down anywhere.
With no argument, v tool lists the tool modules of the project and of the
global module folders, so the names do not have to be remembered:
$ v tool
myproject
mytool
A module is a tool when its root holds a main.v. A library module asked for
by name is refused rather than attempted, and a name that resolves to nothing is an
error.
Running a tool builds and starts the module, which means running code from a
module in the module search path. That is the same trust that v install already
places in a module you chose to install, but it is worth knowing before running a
name you did not install yourself.
Profiling
V has good support for profiling your programs: v -profile profile.txt run file.v.
That will produce a profile.txt file when the program exits, which you can then
analyze. If the output file is omitted, as in v -profile run file.v, the report
is written to standard output. -prof is an alias for -profile.
The compiler supports profiling with its C backend. Other backends reject
-profile. It also supports these compatibility selection options:
-profile-fns name1,name2profiles only the named functions and functions called from them. Use the function names shown in profile output, such asmain__work.-profile-no-inlineomits functions marked@[inline]from the report.-d no_profile_startupexcludes calls made during module initialization.
Use the selection options together with -profile. The generated profile file
has lines with 5 columns:
- How many times a function was called.
- How much time in total a function took (in ms).
- How much time a function took (in ms), on its own, without the calls inside it. It is reliable for multithreaded programs, when tcc is not used.
- How much time on average, a call to a function took (in ns).
- The name of the v function.
You can sort on column 3 (exclusive time per function) using:
sort -n -k3 profile.txt | tail
You can also use stopwatches to measure just portions of your code explicitly: