V and C
The basic mapping between C and V types is described in C and V Type Interoperability.
Calling C from V
V currently does not have a parser for C code. That means that even
though it allows you to #include existing C header and source files,
it will not know anything about the declarations in them. The #include
statement will only appear in the generated C code, to be used by the
C compiler backend itself.
Example of #include
After this statement, V will not know anything about the functions and
structs declared in stdio.h, but if you try to compile the .v file,
it will add the include in the generated C code, so that if that header file
is missing, you will get a C error (you will not in this specific case, if you
have a proper C compiler setup, since <stdio.h> is part of the
standard C library).
To overcome that limitation (that V does not have a C parser), V needs you to
redeclare the C functions and structs, on the V side, in your .c.v files.
V functions can share names such as mktemp and truncate with C library functions.
Use the C. prefix to refer to the C function.
Note that such redeclarations only need to have enough details about the
functions/structs that you want to use.
Note also that they do not have to be complete, unlike the ones in the .h files.
Parameter names in C. function declarations may start with uppercase letters, as in C headers.
The lowercase naming rule still applies to parameters of ordinary V functions.
Fields of a C struct returned by value can be accessed directly on the call, for example
C.get_point(5).row, without first assigning the result to a variable.
An escaped C field name such as @type also matches a binding declared with the plain name type.
An exact escaped V field takes precedence, including fields promoted from embedded structs.
The C keyword fallback follows the C field's owning embed for both its type and its storage.
C. struct redeclarations For example, if a struct has 3 fields on the C side, but you want to only refer to 1 of them, you can declare it like this:
Example of C struct redeclaration
On macOS, an #include or #import of <Cocoa/Cocoa.h>, <AppKit/AppKit.h> or
<AppKit/NSFont.h> makes an opaque C.NSFont declaration refer to Cocoa's Objective-C class.
Another feature, that is very frequently needed for C interoperability,
is the @[typedef] attribute. It is used for marking C. structs,
that are defined with typedef struct SomeName { ..... } TypeName; in the C headers.
For that case, you will have to write something like this in your .c.v file:
Note that the name of the C. struct in V, is the one after the struct SomeName {...}.
This attribute is also required for anonymous C typedefs such as
typedef struct { int x; } Foo;. V does not infer typedef declarations by scanning
headers included with #include or #insert. Native header dependency tracking
and declaration ownership belong to the C compiler. A build whose own code
includes or inserts a C header or source that is not shipped with V, uses
#pkgconfig, or whose C flags name native files or include directories outside
the V installation, bypasses the V module-object and whole-program caches, and
v crun or a .vsh script rebuilds it on every run. System headers
(#include <...>) and declaration-only headers shipped with V keep the caches
enabled. A module shipped with V that compiles a C source, or the implementation
of a single-header library, into the program (as gg, sokol, compress.szip
and compress.zstd do) makes the build bypass the caches too, but v crun still
reuses its executable while none of its inputs change.
If a header needs Objective-C syntax, select the language with #flag -x objective-c;
V does not infer the language from header contents.
C. function redeclarations
The situation is similar for C. functions. If you are going to call just 1 function in a
library, but its .h header declares dozens of them, you will only need to declare that single
function, for example:
Example of C function redeclaration
... and then later, you will be able to call the same way you would V function:
A fixed-array parameter in a C. declaration follows C's pointer adjustment:
fn C.load_matrix(values [16]f32) accepts a matching &f32 or voidptr, including
a dynamic array's .data. Ordinary V fixed-array parameters still require array values.
Typed pointers must use the C element representation. For a C int array declared as
fn C.sum(values [2]int) int, use i32 storage such as values := [i32(10), 20] and
pass &values[0]; V's platform-width int storage is incompatible on 64-bit targets.
Pointer constants such as C.NULL and C.INVALID_HANDLE_VALUE retain their pointer type
in assignments and comparisons.
C globals can be exposed on the V side too. Use @[c_extern] __global name C.Type
when you want to redeclare an external symbol explicitly, or
@[c_extern] __global const name C.Type for an external extern const symbol.
When the type already comes from the V context, direct references like
buffer = C.buffer and [4]u8(C.buffer) can be used as well.
Example of using a C function from stdio, by redeclaring it on the V side
If your C backend compiler is properly setup, you should see something like this, when you try to run it:
#0 10:42:32 /v/examples> v run a.v
Hello world, value: 12345
[a.v:8] x: 26
#0 10:42:33 /v/examples>
Note, that the C function redeclarations look very similar to the V ones, with some differences:
- They lack a body (they are defined on the C side) .
- Their names start with
C.. - Their names can have capital letters (unlike V ones, that are required to use snake_case) .
Note also the second parameter const char *format, which was redeclared as const_format &char .
The const_ prefix in that redeclaration may seem arbitrary, but it is important, if you want
to compile your code with -cstrict or thirdparty C static analysis tools. V currently does not
have another way to express that this parameter is a const (this will probably change in V 1.0).
The const_ convention also applies to C callback function types and aliases. For example,
type NativeCallback = fn (const_buf &u8, len int) int retains the const buffer qualifier.
When you pass a V function by name to a fn C. callback parameter, V adapts its parameter
and return types to the C ABI.
For some C functions, that use variadics (...) as parameters, V supports a special syntax for
the parameters - ...voidptr, that is not available for ordinary V functions (V's variadics are
required to have the same exact type). Usually those are functions of the printf/scanf family
i.e for printf, fprintf, scanf, sscanf, etc, and other formatting/parsing/logging
functions.
Example
Calling V from C
Since V can compile to C, calling V code from C is very easy, once you know how.
Use v -o file.c your_file.v to generate a C file, corresponding to the V code.
More details in call_v_from_c example.
Passing C compilation flags
Add #flag directives to the top of your V files to provide C compilation flags like:
-Ifor adding C include files search paths-lfor adding C library names that you want to get linked-Lfor adding C library files search paths-Dfor setting compile time variables
You can pass a local source file with #flag "@VMODROOT/my_test_cshim.c".
Lowercase .c sources compile as C; uppercase .C, .cc, and .cpp sources compile as C++.
An explicit #flag -x c or #flag -x c++, including the joined forms -xc and -xc++,
overrides the filename's language until #flag -x none restores inference from the filename.
Both spellings also select the matching native compilation standard and runtime libraries,
including when a .o flag compiles an adjacent source into the object cache.
Native C sources and object files from #flag are linked before the libraries from
all modules, including imported modules. Library flags retain their relative order.
Explicit -x language settings remain attached to native inputs when they are reordered.
The final language setting also applies to sources passed later through -ldflags.
You can also use #flag directives, to link to static C libraries, which
will be added last (note the .a suffix):
When a module links a library by a path ending in .a, .so, .dylib, or .lib,
V emits prototypes for its fn C. declarations if the module includes no C headers.
When the module includes a header, the header supplies those declarations instead.
If you need to reverse the order (prepend the library in the libs section of the C compilation line, before other libs), use:
You can (optionally) use different flags for different targets. Every OS listed in Compile time code is supported as flags.
[!NOTE] Each flag must go on its own line (for now)
In the console build command, you can use:
-ccto change the default C backend compiler.-cflagsto pass custom flags to the backend C compiler (passed before other C options).-ldflagsto pass custom flags to the backend C linker (passed after every other C option).-parallel-ccto compile generated C units concurrently with a compatible C compiler.- For example:
-cc gcc-9 -cflags -fsanitize=thread.
Parallel C builds keep the signal-handler runtime and its saved signal actions in one unit. Module-cache builds keep that runtime in the program prefix; cached objects use its declarations. Native headers that cannot safely share state across units use a single compilation unit.
To select C23 with a compiler that supports it, use
v -cc gcc -cflags '-std=gnu23' program.v. Generated C uses the standard boolean keywords
in C23 and supplies compatibility definitions for older C dialects.
You can define a VFLAGS environment variable in your terminal to store your -cc
and -cflags settings, rather than including them in the build command each time.
#pkgconfig
Add #pkgconfig directives to tell the compiler which modules should be used for compiling
and linking using the pkg-config files provided by the respective dependencies.
Resolving the directive runs the pkg-config command, so that command has to be installed and has
to succeed. When it fails — it is not installed, the package is unknown, a dependency does not
resolve — the default C generator contributes no flag at all for that directive, and reports
nothing. If the flags it would have supplied are needed and come from nowhere else, the build then
fails further along, on a header the compiler cannot find or on a symbol the linker cannot resolve.
A #pkgconfig directive is therefore best guarded, so that the program still names its flags when
resolution fails. This is what the standard library modules that bind an external library do.
If no flags are passed it will add --cflags and --libs to pkgconfig (not to V).
In other words, both lines below do the same:
The .pc files are looked up in pkg-config's own default paths, the user can add extra paths by
using the PKG_CONFIG_PATH environment variable. Multiple modules can be passed.
To check the existence of a pkg-config use $pkgconfig('pkg') as a compile time "if" condition to
check if a pkg-config exists. If it exists the branch will be created. Use $else or $else $if
to handle other cases.
The condition runs the same command, so it reports every package as absent when pkg-config is
missing. That is what makes it a usable guard: when the command cannot run, or the probe for a
package fails, the $else branch is taken and can supply fallback flags.
vlib/db/sqlite/sqlite.c.v guards its directive that way, naming the flags itself when sqlite3
does not resolve (abridged here, it has a windows branch too):
When a directive names several packages, probe them all. A guard on one of them still enters the directive when another is the one missing, and loses the flags there.
Including C code
You can also include C code directly in your V module. For example, let's say that your C code is located in a folder named 'c' inside your module folder. Then:
- Put a v.mod file inside the toplevel folder of your module (if you
created your module with
v newyou already have v.mod file). For example:
- Add these lines to the top of your module:
[!NOTE] @VMODROOT will be replaced by V with the nearest parent folder, where there is a v.mod file. Any .v file beside or below the folder where the v.mod file is, can use
#flag @VMODROOT/abcto refer to this folder. The @VMODROOT folder is also prepended to the module lookup path, so you can import other modules under your @VMODROOT, by just naming them.
The instructions above will make V look for an compiled .o file in
your module folder/c/implementation.o.
If V finds it, the .o file will get linked to the main executable, that used the module.
If it does not find it, V assumes that there is a @VMODROOT/c/implementation.c file,
and tries to compile it to a .o file, then will use that.
This allows you to have C code, that is contained in a V module, so that its distribution is easier. You can see a complete minimal example for using C code in a V wrapper module here: project_with_c_code. Another example, demonstrating passing structs from C to V and back again: interoperate between C to V to C.
C types
V methods declared on a C struct can be called through imported fields and local copies of that struct. The method retains the visibility of its declaring V module. Calls see methods from directly imported modules by their full module path. If a V alias of that C struct declares the same method, calls on the alias use its own method.
C-backed struct aliases can also initialize constants, including when compiling with MSVC.
Ordinary zero terminated C strings can be converted to V strings with
unsafe { &char(cstring).vstring() } or if you know their length already with
unsafe { &char(cstring).vstring_with_len(len) }.
[!NOTE] The
.vstring()and.vstring_with_len()methods do NOT create a copy of thecstring, so you should NOT free it after calling the method.vstring(). If you need to make a copy of the C string (some libc APIs likegetenvpretty much require that, since they return pointers to internal libc memory), you can usecstring_to_vstring(cstring).
On Windows, C APIs often return so called wide strings (UTF-16 encoding).
These can be converted to V strings with string_from_wide(&u16(cwidestring)) .
V has these types for easier interoperability with C:
voidptrfor C'svoid*,&u8for C'sbyte*and&charfor C'schar*.&&charfor C'schar**
To cast a voidptr to a V reference, use user := &User(user_void_ptr).
Passing unsafe { nil } to a pointer parameter passes a null pointer, including pointers to
handles that alias voidptr.
Reference results of generic calls also pass the pointer value to voidptr parameters.
For example, isnil(identity[&User](unsafe { nil })) tests the returned pointer for null.
A mutable block that yields &T can pass that pointer to a mut T parameter,
including generic functions and functions from imported modules.
voidptr can also be dereferenced into a V struct through casting: user := User(user_void_ptr).
an example of a module that calls C code from V
C Declarations
C identifiers are accessed with the C prefix similarly to how module-specific
identifiers are accessed. Functions must be redeclared in V before they can be used.
Any C types may be used behind the C prefix, but types must be redeclared in V in
order to access type members.
To redeclare complex types, such as in the following C code:
members of sub-data-structures may be directly declared in the containing struct as below:
The existence of the data members is made known to V, and they may be used without re-creating the original structure exactly.
Alternatively, you may embed the sub-data-structures to maintain a parallel code structure.
Export to shared library
By default all V functions have the following naming scheme in C: [module name]__[fn_name].
For example, fn foo() {} in module bar will result in bar__foo().
To use a custom export name, use the @[export] attribute:
@[export: 'my_custom_c_name']
fn foo() {
}
The same @[export] attribute exposes a __global variable from a shared library,
including builds that hide other symbols by default.
When compiling a Windows DLL with -shared, V generates a default DllMain
that calls _vinit_caller() on DLL_PROCESS_ATTACH and _vcleanup_caller()
on DLL_PROCESS_DETACH.
If you export your own DllMain, V will not generate the default one. Call
C._vinit_caller() and C._vcleanup_caller() from your entry point to keep
the standard V runtime setup and teardown:
In the example above, C.DWORD(1) is DLL_PROCESS_ATTACH and C.DWORD(0)
is DLL_PROCESS_DETACH.
Translating C to V
Files marked @[translated] retain C storage rules: global declarations and writes through
pointers do not require additional flags or unsafe blocks. These rules apply only to those files.
Pointer-returning calls can also receive field assignments.
Pointers to char, i8, and u8 of the same pointer depth are interchangeable in translated
assignments, returns, function arguments, and other typed values. Ordinary V files retain their
pointer type checks; calls to C functions also accept these character pointers.
Files marked @[translated] retain C scalar conversions between numbers, enums, and booleans.
These scalars can be mixed in arithmetic expressions and compound assignments. Integral scalars
can be used in bitwise expressions. Scalar values and pointers, including function pointers,
can serve as conditions. Ordinary V files retain V's type and condition checks, even when
compiled together with translated files.
Conversions to translated int use the target C int width at assignments, calls, and returns.
Mixed numeric compound assignments use C arithmetic conversions before storing their result.
This includes rune as an unsigned 32-bit integer and enums with their declared backing types.
Files marked @[translated] module ... also accept expression conditions generated by C2V,
including nested if expressions and subtraction of a negative operand. Postfix pointer
updates followed by a dereference assignment on the next line end the current statement,
including compound assignments.
Other arithmetic continues across the newline. A translated sizeof recognizes constant
operands even when their declarations appear later in the module.
Only source files selected for the target and compile-time defines contribute declarations.
Lowercase type aliases declared in the same file remain type operands of sizeof.
Translated local C variables can be updated without an explicit mut declaration.
Code generated by other compilers often needs only V's naming rules relaxed, without the C
semantics above. Mark such a module with @[generated] instead:
The attribute has to be put on an explicit module line, module main included, and it
applies only to the file it is in, so mark every generated file of a module. In such a file,
names of functions, methods, variables, parameters, constants, fields and globals can use
camelCase and start with _, and type names do not have to start with a capital letter. Only
type names can start with an uppercase letter, because V relies on that to tell Type{} and
Type(x) apart from values.
A few names stay invalid, because the generated C code needs them: no name can contain __,
type names cannot end with _, and other names cannot start with _ and an uppercase letter,
or consist of _, lowercase letters and digits like _t1. A type cannot share its name with a
function or constant of its module or with a builtin function, and interfaces cannot have the
fields _typ and _object. Keywords stay reserved, module names keep the usual rules, and
everything else, including type checks and mutability, works exactly as in ordinary V files.
Casts like t(x) to a type whose name starts in lower case work inside its module.
V can translate your C code to human readable V code, and generating V wrappers on top of C libraries.
C2V currently uses Clang's AST to generate V, so to translate a C file to V you need to have Clang installed on your machine.
Let's create a simple program test.c first:
Run v translate test.c, and V will generate test.v:
To generate a wrapper on top of a C library use this command:
v translate wrapper c_code/libsodium/src/libsodium
This will generate a directory libsodium with a V module.
Example of a C2V generated libsodium wrapper:
https://github.com/vlang/libsodium
When should you translate C code and when should you simply call C code from V?
If you have well-written, well-tested C code, then of course you can always simply call this C code from V.
Translating it to V gives you several advantages:
- If you plan to develop that code base, you now have everything in one language, which is much safer and easier to develop in than C.
- Cross-compilation becomes a lot easier. You don't have to worry about it at all.
- No more build flags and include files either.
Working around C issues
In some cases, C interop can be extremely difficult. One of these such cases is when headers conflict with each other. For example, V needs to include the Windows header libraries in order for your V binaries to work seamlessly across all platforms.
However, since the Windows header libraries use extremely generic names such as Rectangle,
this will cause a conflict if you wish to use C code that also has a name defined as Rectangle.
V defaults to WIN32_LEAN_AND_MEAN for its built-in Windows headers, including those loaded
through the garbage collector. This excludes optional headers such as OLE and multimedia headers.
Include any required optional Windows headers explicitly, or use #flag windows -DWIN32_FULL
to request the full Windows header surface. A configuration preinclude can also define WIN32_FULL.
For very specific cases like this, V has #preinclude and #postinclude directives.
These directives allow things to be configured before V adds in its built in libraries, and after all of the V code generation has completed (and thus all of the prototypes, declarations and definitions are already present).
Example usage:
An example of what might be included in pre_include.h
can be found here
The #postinclude directive on the other hand is useful for allowing the integration
of frameworks like SDL3 or Sokol, that insist on having callbacks in your code, instead
of behaving like ordinary libraries, and allowing you to decide when to call them.
NOTE: these are advanced features, and will not be necessary outside of very specific cases with C interop. Other than those, using them could cause more issues than it solves.
Consider using them as a last resort!