V nació pensado para hablar con C sin fricción: el propio compilador transpila (en su backend clásico) a C antes de generar el binario final, así que la interoperabilidad no es un añadido tardío sino parte del diseño desde el primer día. Esto se nota en lo directo que es llamar código C desde V, mucho más parecido a escribir C con otra sintaxis que al cgo de Go, que necesita un compilador C separado invocado como subproceso.
Declarar e importar funciones C
V no parsea cabeceras C. Si quieres usar una función de una librería C, la rededclaras tú mismo con el prefijo C.:
#include <stdio.h>
fn C.dprintf(fd int, const_format &char, ...voidptr) int
fn main() {
value := 12345
x := C.dprintf(0, c'Hola mundo, valor: %dn', value)
}
Dos detalles que llaman la atención: los strings literales para C llevan el prefijo c'...', distinto de las comillas normales de V, y las funciones con el prefijo C. pueden tener nombres con mayúsculas aunque V exige snake_case en el resto del código.
Structs de C
@[typedef]
pub struct C.TypeName {}
struct C.NombreDelStruct {
un_campo int
}
Igual que con las funciones, solo hace falta redeclarar los campos que realmente vas a usar, no la estructura completa tal cual está en la cabecera original.
Flags de compilación
#flag linux -lsdl2 #flag linux -I/ruta/a/includes #flag /ruta/a/libreria.a
Los #flag son la forma de decirle al compilador C subyacente qué librerías enlazar y dónde buscar cabeceras, con variantes específicas por sistema operativo. También hay soporte de pkg-config directo:
#pkgconfig r_core #pkgconfig --cflags --libs mysql
Compilar ficheros C junto a tu proyecto
Con un v.mod en la raíz del proyecto, puedes referenciar ficheros .c propios y V los compila automáticamente a .o si hace falta:
#flag -I @VMODROOT/c #flag @VMODROOT/c/implementacion.o #include "cabecera.h"
De C a V automáticamente
Si lo que tienes es una librería C entera y no quieres redeclarar función por función, existe v translate, que usa Clang para generar V legible a partir del código fuente C:
v translate archivo.c v translate wrapper c_code/libsodium/src/libsodium
No es magia perfecta (el código traducido suele necesitar retoques manuales), pero ahorra mucho trabajo mecánico cuando quieres portar una librería pequeña en vez de mantenerla como dependencia C externa para siempre.
Conversión de tipos entre V y C
voidptr // equivale a void*
&u8 // equivale a char* sin signo
&char // equivale a char*
&&char // equivale a char**
// convertir un string C a un string V
resultado := unsafe { cstring_to_vstring(c_str) }
Esta zona de conversión de punteros es la única parte del lenguaje donde necesitas el bloque unsafe, un recordatorio de que, en cuanto cruzas la frontera con C, las garantías de memoria que vimos en el segundo artículo de esta serie dejan de aplicar: a partir de ahí, la responsabilidad es tuya, igual que en C puro.
Si vienes de Go y ya conoces cgo, la comparación es instructiva: cgo invoca un compilador C real como parte del proceso de build y tiene un coste de rendimiento notable al cruzar la frontera Go/C en cada llamada (lo cubrimos con detalle en este artículo sobre cgo); V, al compilar hacia C en su backend clásico, no tiene esa frontera en absoluto, tu código y el código C conviven en el mismo binario nativo desde el principio. Si además necesitas repasar la gestión manual de memoria en C con Valgrind, tenemos esta guía. En el siguiente artículo de la serie tocamos testing: v test, assert y cómo estructurar los tests en un proyecto V.
Imagen: Pexels / Rafael Minguet Delgado
