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.
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.
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
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 {...}
.
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:
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 simillar 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).
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:
-I
for adding C include files search paths-l
for adding C library names that you want to get linked-L
for adding C library files search paths-D
for setting compile time variables
You can (optionally) use different flags for different targets.
Currently the linux
, darwin
, freebsd
, and windows
flags are supported.
[!NOTE] Each flag must go on its own line (for now)
In the console build command, you can use:
-cc
to change the default C backend compiler.-cflags
to pass custom flags to the backend C compiler (passed before other C options).-ldflags
to pass custom flags to the backend C linker (passed after every other C option).- For example:
-cc gcc-9 -cflags -fsanitize=thread
.
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.
As long as backticks can't be used in #flag
and spawning processes is not desirable for security
and portability reasons, V uses its own pkgconfig library that is compatible with the standard
freedesktop one.
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 into a hardcoded list of default pkg-config 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.
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 new
you 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/abc
to 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
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 likegetenv
pretty 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:
voidptr
for C'svoid*
,&u8
for C'sbyte*
and&char
for C'schar*
.&&char
for C'schar**
To cast a voidptr
to a V reference, use user := &User(user_void_ptr)
.
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() {
}
Translating C to V
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
.
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!