Conditional compilation
The goal of this feature, is to tell V to not compile a function, and all its calls, in the final executable, if a provided custom flag is not passed.
V will still type check the function and all its calls, even if they will not be present in the final executable, due to the passed -d flags.
In order to see it in action, run the following example with v run example.v once,
and then a second time with v -d trace_logs run example.v:
Conditional compilation, based on custom flags, can also be used to produce slightly different executables, which share the majority of the same code, but where some of the logic, is needed only some of the time, for example a network server/client program can be written like so:
To generate a client.exe executable do: v -d as_client -o client.exe .
To generate a server.exe executable do: v -d as_server -o server.exe .
Compile time pseudo variables
V also gives your code access to a set of pseudo string variables, that are substituted at compile time:
@FN=> replaced with the name of the current V function.@METHOD=> replaced with ReceiverType.MethodName.@MOD=> replaced with the name of the current V module.@STRUCT=> replaced with the name of the current V struct.@FILE=> replaced with the absolute path of the V source file.@DIR=> replaced with the absolute path of the folder, where the V source file is.@LINE=> replaced with the V line number where it appears (as a string).@FILE_LINE=> like@FILE:@LINE, but the file part is a relative path.@LOCATION=> file, line and name of the current module + function or method; suitable for logging.@COLUMN=> replaced with the column where it appears (as a string).@VEXE=> replaced with the path to the V compiler.@VEXEROOT=> will be substituted with the folder, where the V executable is (as a string).@VHASH=> replaced with the shortened commit hash of the V compiler (as a string).@VCURRENTHASH=> Similar to@VHASH, but changes when the compiler is recompiled on a different commit (after local modifications, or after using git bisect etc).@VMOD_FILE=> replaced with the contents of the nearest v.mod file (as a string).@VMODHASH=> is replaced by the shortened commit hash, derived from the .git directory next to the nearest v.mod file (as a string).@VMODROOT=> will be substituted with the folder, where the nearest v.mod file is (as a string).@BUILD_DATE=> replaced with the build date, for example '2024-09-13' .@BUILD_TIME=> replaced with the build time, for example '12:32:07' .@BUILD_TIMESTAMP=> replaced with the build timestamp, for example '1726219885' .@OS=> replaced with the OS type, for example 'linux' .@CCOMPILER=> replaced with the C compiler type, for example 'gcc' .@BACKEND=> replaced with current language backend, for example 'c' or 'golang' .@PLATFORM=> replaced with the platform type, for example 'amd64' .
Note: @BUILD_DATE, @BUILD_TIME, @BUILD_TIMESTAMP represent times in the UTC timezone.
By default, they are based on the current time of the compilation/build. They can be overridden
by setting the environment variable SOURCE_DATE_EPOCH. That is also useful while making
releases, since you can use the equivalent of this in your build system/script:
export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) ; , and then use @BUILD_DATE etc.,
inside your program, when you for example print your version information to users.
See also https://reproducible-builds.org/docs/source-date-epoch/ .
The compile time pseudo variables allow you to do the following example, which is useful while debugging/logging/tracing your code:
Another example, is if you want to embed the version/name from v.mod inside your executable:
A program that prints its own source code (a quine):
[!NOTE] you can have arbitrary source code in the file, without problems, since the full file will be embedded into the executable, produced by compiling it. Also note that printing is done with
println, to not add another new line, missing in the source code.
A program that prints the time when it was built:
Compile time reflection
$ is used as a prefix for compile time (also referred to as 'comptime') operations.
Having built-in JSON support is nice, but V also allows you to create efficient
serializers for any data format. V has compile time if and for constructs:
Type metadata
Comptime type expressions expose metadata through fields like .idx, .typ,
.unaliased_typ, .indirections, .key_type, .value_type, .element_type,
.pointee_type, .payload_type, and .variant_types.
$zero(Type) returns the zero value for a comptime type expression.
$new(Type) returns a pointer to a new zero value.
.fields
You can iterate over struct fields using .fields, it also works with generic types
(e.g. T.fields) and generic arguments (e.g. param.fields where fn gen[T](param T) {).
Each field's .attrs is an array of strings. Inside the reflection loop, you can use
for attr in field.attrs or for index, attr in field.attrs to process these strings at runtime,
including calls such as attr.split_any(':'). The index has type int.
A $if in a reflection loop is decided at compile time, separately for each item. Its
condition can compare the loop variable's metadata with literals (==, !=, <, >, <=,
>=, in), check types with is, test names with .starts_with(), .ends_with(),
.contains() and .len, and combine those with &&, || and !.
Pure string method chains on literal or substituted reflection strings also support
all_before, all_after, all_before_last, all_after_last, trim, trim_left,
trim_right, trim_space, trim_string_left, trim_string_right, replace,
to_lower, to_upper, and count, when every argument is a compile-time-known string.
These scalar operations also fold in ordinary expressions and constant initializers. Operands
may be literals, reflection strings, constants, or immutable locals initialized from them.
Static string slices (text[start..end]) and .len also fold. Mutable locals stay runtime;
using one in a compile-time condition is an error.
$for can iterate split, split_any, or fields of a compile-time-known string. Each
iteration binds its variable to a string literal in its own scope. The compiler unrolls the
body without allocating an array or parsing the string at runtime:
A source containing runtime operands or unsupported operations is an error. String arrays
from these calls are folded only as $for sources. A condition that cannot be decided at
compile time is reported as an error:
.values
You can read Enum values and their attributes.
The e.value member contains the enum's actual value, including values initialized from C
constants. These C-derived values remain symbolic until C compilation, so they can be used
in runtime expressions but cannot be evaluated in a $if condition by the V compiler.
This also applies to copied members and implicit successors. The source enum may be declared
after an enum that copies its members. An explicit initializer with a value known to V restores
compile-time evaluation for that member and its successors.
Symbolic arithmetic preserves integer storage widths and V operator grouping, including logical
right shifts and zero results for negative or out-of-range shift counts. Powers and @[comptime]
function calls in enum initializers must be evaluable by V at compile time; they cannot depend on
C constants. Known calls are evaluated even when used beside a C constant in symbolic arithmetic.
These enum helpers support straight-line integer declarations, plain assignments, and one final
single-value return. Bodies containing control flow, other statements, or unevaluable locals are
rejected instead of partially evaluated. Imported helpers resolve their constants and types in their
declaration file and module; their arguments are evaluated in the calling enum's context.
Integer-to-enum cast warnings require every member's
numeric value to be known to V; they are omitted for enums containing C-derived members.
.attributes
You can read Struct attributes.
.variants
You can read variant types from Sum type.
Within a variant loop, if value is variant narrows expressions to the current payload type.
Conjunctions and nested guards retain all active refinements. A mutable guard keeps the original
sum storage available for reassignment. Each iteration infers its own local collection types;
conditions using enclosing reflection metadata are checked after that metadata is substituted.
Numeric comparisons with variant.typ use the same type index as value.type_idx().
Optional and result variants expose their wrapped type's metadata index, preserving pointer depth.
Conditions using local metadata values are checked after unrolling, when their values are available.
Matches retain their arm refinements while loop metadata is checked.
Declaration guards such as if payload := value bind the unwrapped payload in their success branch.
Exiting option guards also unwrap payloads for later statements in the same block.
Each guard applies to the option layer visible before it.
Type checks in loop conditions, assertions, and exiting guards preserve the optional payload type.
.methods
You can retrieve information about struct methods. Reflected method calls execute the ordinary method body, including calls into imported modules and generic helper functions.
.params
You can retrieve information about struct method params.
Generic comptime method calls enforce the same mutable pointer parameter requirements
as calls through a concrete receiver type, including calls forwarded through generic functions
and methods. An explicit mut param &T requires a mutable &T variable, rather than a
mut param T value parameter.
A reflected method call can pass explicit arguments followed by ...args to supply the remaining
parameters. The spread can be empty when the method has no remaining parameters. Explicit arguments
before the spread still follow the method's mut parameter requirements.
Trailing parameters declared as ?T can be omitted from a reflected method call.
An alias of an option type still requires an explicit argument.
Inside a .methods reflection loop, method.args is a runtime array of FunctionParam records.
Runtime loops over slices such as method.args[1..] retain each parameter's name and typ.
Runtime dispatch can select calls with different arities using method.args.len or
method.params.len guards, including guards combined with runtime conditions. In these branches,
calls incompatible with the current method's arity are omitted. Compatible calls still enforce
mutable parameter requirements. Methods with an implicit veb context accept an explicit context
before their declared route arguments; that inserted context is absent from method.args metadata.
See examples/compiletime/reflection.v
for a more complete example.
Compile time code
$if condition
If you want an if to be evaluated at compile time it must be prefixed with a $ sign.
Right now it can be used to detect an OS, compiler, platform or compilation options.
$if debug is a special option like $if windows or $if x32, it's enabled if the program
is compiled with v -g or v -cg.
If you're using a custom ifdef, then you do need $if option ? {} and compile withv -d option.
Full list of builtin options:
| OS | Compilers | Platforms | Other |
|---|---|---|---|
windows, linux, macos |
gcc, tinyc |
amd64, arm64, aarch64 |
debug, prod, test |
darwin, ios, bsd |
clang, mingw |
i386, arm32 |
js, glibc, prealloc |
freebsd, openbsd, netbsd |
msvc |
rv64, rv32, s390x |
no_bounds_checking, freestanding |
android, mach, dragonfly |
cplusplus |
ppc64le |
no_segfault_handler, no_backtrace |
gnu, hpux, haiku, qnx |
x64, x32 |
no_main, fast_math, apk, threads |
|
solaris, termux |
little_endian, big_endian |
js_node, js_browser, js_freestanding |
|
serenity, vinix, plan9 |
interpreter, es5, profile, wasm32 |
||
wasm32_emscripten, wasm32_wasi |
|||
native, autofree |
glibc and musl describe the C library the generated program is linked against.
On a native Linux build that V compiles and links itself, V infers the host libc.
That host inference is deliberately not carried into C-only or object output, generated C
projects, portable -os cross output, or a foreign target, because another toolchain may
link those artifacts. Pass -glibc or -musl when that target libc is known; -cc musl-gcc also implies -musl. The latter enables optional checks such as $if musl ? {.
$embed_file
V can embed arbitrary files into the executable with the $embed_file(<path>)
compile time call. Paths can be absolute or relative to the source file.
Paths could also use the compile time pseudo variables @VEXEROOT,
@VMODROOT, @DIR, $d and $env.
The whole content of the file is embedded only in -prod builds (and in portable
-os cross C output). A normal development build stores just the file's path and
the file is loaded from that path the first time your program calls
embedded_file.data() at runtime. This keeps rebuilds cheap and lets you change
the file in an external editor without recompiling your program.
embedded_file.len is the size of the file when the program was compiled, until
the file is loaded. After that it is the size of what was loaded.
Because the stored path points to the machine the program was built on, a
development build panics when it runs where that file does not exist, for example
on another computer, or after cross compiling for another OS. Use -prod for
anything you distribute.
Embedding a file inside your executable, will increase its size, but
it will make it more self contained and thus easier to distribute.
When that happens (with -prod), embedded_file.data() will cause no IO,
and it will always return the same data.
With -prod, a large embedded file is stored through the assembler's .incbin
directive: V writes the bytes to a file, assembles a small .S source that
includes it, and links the resulting object next to the generated C, so the C
compiler never has to parse the bytes as an array initializer. That happens when
the build links natively with GCC, Clang or MinGW, or with TCC targeting the
host on systems other than macOS and Windows when a GCC or Clang compatible
compiler is installed.
On ELF targets, the payload object marks its stack as non-executable.
Generated C or object output (-o file.c, -o file.o, -generate-c-project),
MSVC, iOS and WebAssembly targets, and a Windows target built on another OS keep
the array form. TCC builds on macOS and Windows, and TCC builds targeting another
OS or architecture, also keep it.
-keepc, an explicit -b c, and -dump-c-flags also keep the array form so
their retained output does not depend on temporary object files. -d no_incbin
selects it everywhere.
$embed_file supports compression of the embedded file when compiling with -prod.
Currently only one compression type is supported: zlib.
Note: compressing binary assets like png or zip files, usually will not gain you much, and in some cases may even take more space in the final executable, since they are already compressed.
$embed_file returns
EmbedFileData
which could be used to obtain the file contents as string or []u8.
Its .data() method also accepts immutable values and constants, returning a byte pointer.
Use the returned value: discarding $embed_file as a statement is an error, including
when it is the fallback value of an unused or expression with nested or blocks.
Passing it as a call argument consumes the value, even when the call has an or block.
$tmpl for embedding and parsing V template files
V has a simple template language for text and html templates, and they can easily
be embedded via $tmpl('path/to/template.txt'):
the path can also come from a compile-time string expression like concatenation,
os.path_separator, or os.join_path(...).
1.txt:
name: @name
age: @age
numbers: @numbers
@for number in numbers
@number
@end
output:
name: Peter
age: 25
numbers: [1, 2, 3]
1
2
3
See more details
$env
V can bring in values at compile time from environment variables.
$env('ENV_VAR') can also be used in top-level #flag and #include statements:
#flag linux -I $env('JAVA_HOME')/include.
$d
V can bring in values at compile time from -d ident=value flag defines, passed on
the command line to the compiler. You can also pass -d ident, which will have the
same meaning as passing -d ident=true.
The -ownership compiler mode supplies ownership as a target-visible custom option.
This enables $if ownership ? {} branches and includes *_d_ownership.v files.
To get the value in your code, use: $d('ident', default), where default
can be false for booleans, 0 or 123 for i64 numbers, 0.0 or 113.0
for f64 numbers, 'a string' for strings.
When a flag is not provided via the command line, $d() will return the default
value provided as the second argument.
Running the above with v run . will output:
V
1024
Running the above with v -d my_i64=4096 -d my_string="V rocks" run . will output:
V rocks
4096
Here is an example of how to use the default values, which have to be pure literals:
$d('ident','value') can also be used in top-level statements like #flag and #include:
#flag linux -I $d('my_include','/usr')/include. The default value for $d when used in these
statements should be literal strings.
$d('ident', false) can also be used inside $if $d('ident', false) { statements,
granting you the ability to selectively turn on/off certain sections of code, at compile
time, without modifying your source code, or keeping different versions of it.
$compile_error and $compile_warn
These two comptime functions are very useful for displaying custom errors/warnings during compile time.
Top-level compile errors and warnings in imported modules are evaluated even when none of their functions is called.
Both receive as their only argument a string literal that contains the message to display:
Compile time types
Compile time types group multiple types into a general higher-level type. This is useful in
functions with generic parameters, where the input type must have a specific property, for example
the .len attribute in arrays.
V supports the following compile time types:
$alias=> matches Type aliases.$array=> matches Arrays and Fixed Size Arrays.$array_dynamic=> matches Arrays, but not Fixed Size Arrays.$array_fixed=> matches Fixed Size Arrays, but not Arrays$enum=> matches Enums.$float=> matchesf32,f64and float literals.$function=> matches Function Types.$int=> matchesint,i8,i16,i32,i64,u8,u16,u32,u64,uintand integer literals.$interface=> matches Interfaces.$map=> matches Maps.$option=> matches Option Types.$shared=> matches Shared Types.$struct=> matches Structs.$sumtype=> matches Sum Types.$string=> matches Strings.$pointer=> matches Reference Types.$voidptr=> matches C'svoid*.
Environment specific files
If a file has an environment-specific suffix, it will only be compiled for that environment.
.js.v=> will be used only by the JS backend. These files can contain JS. code..c.v=> will be used only by the C backend. These files can contain C. code..native.v=> will be used only by V's native backend._nix.c.v=> will be used only on Unix systems (non Windows)._bsd.c.v=> will be used only on macOS, FreeBSD, OpenBSD, NetBSD, and DragonFly._${os}.c.v=> will be used only on the specificossystem. For example,_windows.c.vwill be used only when compiling on Windows, or with-os windows._default.c.v=> will be used only if there is NOT a more specific platform file. For example, if you have bothfile_linux.c.vandfile_default.c.v, and you are compiling for linux, then onlyfile_linux.c.vwill be used, andfile_default.c.vwill be ignored. BSD-specific files are ordered from most to least specific as_${os}.c.v,_bsd.c.v,_nix.c.v, then_default.c.v.
Here is a more complete example:
main.v:
main_default.c.v:
main_linux.c.v:
main_windows.c.v:
With the example above:
when you compile for Windows, you will get
Hello windowswhen you compile for Linux, you will get
Hello linuxwhen you compile for any other platform, you will get the non specific
Hello worldmessage._d_customflag.v=> will be used only if you pass-d customflagto V. That corresponds to$if customflag ? {}, but for a whole file, not just a single block.customflagshould be a snake_case identifier, it can not contain arbitrary characters (only lower case latin letters + numbers +_).Note
A combinatorial
_d_customflag_linux.c.vpostfix will not work. If you do need a custom flag file, that has platform dependent code, use the postfix_d_customflag.v, and then use platform dependent compile time conditional blocks inside it, i.e.$if linux {}etc._notd_customflag.v=> similar to _d_customflag.v, but will be used only if you do NOT pass-d customflagto V.
See also Cross Compilation.