{"schemaVersion":1,"id":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming","url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming","title":"El Comptime de Zig: Un Enfoque Unificado para la Programación de Sistemas en Compile-Time","description":"La ejecución de código en compile-time en Zig reemplaza macros, generics y compilación condicional con un solo mecanismo unificado. Reconstruir un parser de paquetes en Zig revela cómo comptime desafía las suposiciones arraigadas en el mantenimiento tradicional de proyectos C.","language":"es","datePublished":"2026-02-04","dateModified":null,"contentFormat":"text/markdown","content":"Systems Programming\n\n# El Comptime de Zig: Un Enfoque Unificado para la Programación de Sistemas en Compile-Time\n\n2026-02-0411 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/zig-language-comptime-system-programming>)\n\n## Qué Es Realmente Comptime\n\nLa mayoría de los lenguajes tienen alguna forma de evaluación en compile time. C tiene macros ` #define ` y compilación condicional ` #ifdef `. C++ tiene ` constexpr ` y templates. Rust tiene ` const fn `, macros y generics. Cada uno de estos es un mecanismo separado con su propia sintaxis, reglas y limitaciones.\n\n[Zig](<https://ziglang.org/>) tiene un solo mecanismo: ` comptime `. Cualquier expresión puede evaluarse en compile time, y las mismas construcciones del lenguaje funcionan tanto en compile time como en runtime. No hay lenguaje de macros separado, no hay sintaxis de templates, no hay anotaciones de generics. Se escribe código Zig normal y se marcan partes como ` comptime `.\n\n```zig\n// Esta es una función regular que puede correr en compile time O runtime\nfn fibonacci(n: u64) u64 {\n if (n <= 1) return n;\n return fibonacci(n - 1) + fibonacci(n - 2);\n}\n\n// Evaluación en compile time\nconst fib_20 = comptime fibonacci(20); // Computado en compile time: 6765\n\n// Evaluación en runtime\nfn getfib(n: u64) u64 {\n return fibonacci(n); // Computado en runtime\n}\n```\n\nEsto se ve simple, tal vez hasta trivial. Pero las implicaciones se propagan en cascada. Un ejemplo real ilustra el punto.\n\n## Reemplazando Generics con Comptime\n\nEn C, crear una estructura de datos genérica requiere void pointers y macros:\n\n```c\n// C: Array dinámico genérico via macros\n#define DEFINE_VEC(T) \\\n typedef struct { \\\n T* items; \\\n size_t len; \\\n size_t cap; \\\n } Vec_##T; \\\n \\\n void vec_##T##_push(Vec_##T* v, T item) { \\\n if (v->len >= v->cap) { \\\n v->cap = v->cap ? v->cap * 2 : 8; \\\n v->items = realloc(v->items, sizeof(T) * v->cap); \\\n } \\\n v->items[v->len++] = item; \\\n }\n\nDEFINE_VEC(int)\nDEFINE_VEC(float)\n// Genera Vec_int, Vec_float, etc.\n```\n\nEsto es ilegible, imposible de debuggear, y produce mensajes de error espantosos. En Zig, el mismo concepto usa tipos comptime:\n\n```zig\nfn ArrayList(comptime T: type) type {\n return struct {\n items: []T,\n len: usize,\n capacity: usize,\n allocator: std.mem.Allocator,\n\n const Self = @This();\n\n pub fn init(allocator: std.mem.Allocator) Self {\n return Self{\n .items = &[_]T{},\n .len = 0,\n .capacity = 0,\n .allocator = allocator,\n };\n }\n\n pub fn append(self: *Self, item: T) !void {\n if (self.len >= self.capacity) {\n const new_cap = if (self.capacity == 0) 8 else self.capacity * 2;\n self.items = try self.allocator.realloc(self.items, new_cap);\n self.capacity = new_cap;\n }\n self.items[self.len] = item;\n self.len += 1;\n }\n\n pub fn deinit(self: *Self) void {\n self.allocator.free(self.items);\n }\n };\n}\n\n// Uso, el compilador genera un struct especializado para cada tipo\nvar list = ArrayList(u32).init(allocator);\ndefer list.deinit();\ntry list.append(42);\n```\n\nEl parámetro ` comptime T: type ` significa que ` T ` debe ser conocido en compile time. La función devuelve un ` type `, literalmente genera una definición de struct. Esto no es una instanciación de template; es código Zig real ejecutándose que produce un tipo. El cuerpo puede contener sentencias ` if `, loops, o cualquier otra construcción:\n\n```zig\nfn Matrix(comptime T: type, comptime rows: usize, comptime cols: usize) type {\n // Validación en compile time\n if (@typeInfo(T) != .Float and @typeInfo(T) != .Int) {\n @compileError(\"Matrix requiere un tipo numérico, recibió \" ++ @typeName(T));\n }\n\n if (rows == 0 or cols == 0) {\n @compileError(\"Las dimensiones de la matrix deben ser non-zero\");\n }\n\n return struct {\n data: [rows][cols]T,\n\n const Self = @This();\n\n // Generar multiplicación optimizada basada en dimensiones\n pub fn multiply(self: Self, other: Matrix(T, cols, comptime other_cols: usize)) Matrix(T, rows, other_cols) {\n var result: Matrix(T, rows, other_cols) = undefined;\n inline for (0..rows) |i| {\n inline for (0..other_cols) |j| {\n var sum: T = 0;\n inline for (0..cols) |k| {\n sum += self.data[i][k] * other.data[k][j];\n }\n result.data[i][j] = sum;\n }\n }\n return result;\n }\n };\n}\n```\n\nEl ` inline for ` desenrolla los loops en compile time. Para una multiplicación de matrices 4x4, el compilador genera 64 instrucciones multiply-add sin overhead de loop. Esto es el tipo de optimización que requeriría intrinsics o assembly escrito a mano en C.\n\n## El Parser de Paquetes: Un Ejemplo Real\n\nUna demostración convincente del poder de comptime surge al construir un parser de paquetes DNS. Una implementación C típica involucra aritmética de punteros, bounds checking manual, y lógica condicional para distintos tipos de registros, código notoriamente frágil y difícil de mantener.\n\nLa versión Zig usa comptime para generar parsers para distintos tipos de registros DNS:\n\n```zig\nconst DnsRecordType = enum(u16) {\n A = 1,\n AAAA = 28,\n CNAME = 5,\n MX = 15,\n TXT = 16,\n SRV = 33,\n};\n\nfn RecordData(comptime rtype: DnsRecordType) type {\n return switch (rtype) {\n .A => struct { addr: [4]u8 },\n .AAAA => struct { addr: [16]u8 },\n .CNAME => struct { name: []const u8 },\n .MX => struct { priority: u16, exchange: []const u8 },\n .TXT => struct { texts: []const []const u8 },\n .SRV => struct {\n priority: u16,\n weight: u16,\n port: u16,\n target: []const u8,\n },\n };\n}\n\nfn parseRecord(comptime rtype: DnsRecordType, data: []const u8) !RecordData(rtype) {\n var result: RecordData(rtype) = undefined;\n\n switch (rtype) {\n .A => {\n if (data.len < 4) return error.TruncatedPacket;\n result.addr = data[0..4].*;\n },\n .AAAA => {\n if (data.len < 16) return error.TruncatedPacket;\n result.addr = data[0..16].*;\n },\n .MX => {\n if (data.len < 2) return error.TruncatedPacket;\n result.priority = std.mem.readInt(u16, data[0..2], .big);\n result.exchange = try decompressName(data[2..]);\n },\n // ... otros tipos\n }\n\n return result;\n}\n```\n\nEn compile time, si se llama ` parseRecord(.A, some_data) `, el compilador genera una función que solo maneja registros A, las ramas del switch para AAAA, MX, etc. se eliminan completamente. Cero overhead en runtime para el dispatch de tipos.\n\nPero el poder real es la validación en compile time:\n\n```zig\ncomptime {\n // Verificar en compile time que todos los tipos de registro tienen parsers\n inline for (std.meta.fields(DnsRecordType)) |field| {\n const rtype: DnsRecordType = @enumFromInt(field.value);\n // Esto fuerza al compilador a instanciar el parser\n // para cada tipo de registro, atrapando errores en compile time\n _ = RecordData(rtype);\n }\n}\n```\n\nSi se agrega un nuevo tipo de registro al enum pero falta el case correspondiente en el switch de ` RecordData `, el código no compila. En C, eso sería un crash en runtime con el primer paquete del nuevo tipo de registro, probablemente a las 3 AM.\n\n## Manejo de Errores: La Feature Subestimada\n\nLas discusiones sobre Zig tienden a enfocarse en comptime, pero el manejo de errores es igual de importante. Zig usa error unions, un valor es o el tipo esperado o un error:\n\n```zig\nconst ParseError = error{\n TruncatedPacket,\n InvalidHeader,\n LabelTooLong,\n CompressionLoop,\n BufferOverflow,\n};\n\nfn parseDnsMessage(buf: []const u8) ParseError!DnsMessage {\n if (buf.len < 12) return error.TruncatedPacket;\n\n const header = parseHeader(buf[0..12]);\n\n var offset: usize = 12;\n var questions: [MAX_QUESTIONS]Question = undefined;\n\n for (0..header.qdcount) |i| {\n // La keyword `try` propaga errores automáticamente\n questions[i] = try parseQuestion(buf, &offset);\n }\n\n return DnsMessage{\n .header = header,\n .questions = questions[0..header.qdcount],\n };\n}\n```\n\nEl ` ! ` en el tipo de retorno significa \"esta función puede fallar.\" La keyword ` try ` es equivalente a \"si esto devuelve un error, devolvelo de la función actual.\" Sin excepciones, sin códigos de error que se puedan olvidar de chequear, sin estado global ` errno `.\n\nComparación con el equivalente en C:\n\n```c\n// C: Cada caller tiene que acordarse de chequear el valor de retorno\n// Nada impide ignorar errores\nint parse_dns_message(const uint8_t* buf, size_t len, dns_message_t* out) {\n if (len < 12) return DNS_ERR_TRUNCATED;\n\n int err = parse_header(buf, &out->header);\n if (err) return err; // Fácil olvidarse de esta línea\n\n size_t offset = 12;\n for (int i = 0; i < out->header.qdcount; i++) {\n err = parse_question(buf, len, &offset, &out->questions[i]);\n if (err) return err; // Y de esta también\n }\n\n return 0;\n}\n```\n\nEn la práctica, los codebases C frecuentemente contienen retornos de error sin chequear, una categoría de bug que Zig elimina estructuralmente. Si una función devuelve un error union, debe manejarse.\n\n## Interop con C: La Killer Feature Práctica\n\nZig puede importar headers C directamente. No a través de FFI bindings, no a través de un paso de generación, directamente:\n\n```zig\nconst c = @cImport({\n @cInclude(\"pcap/pcap.h\");\n @cInclude(\"netinet/ip.h\");\n @cInclude(\"netinet/udp.h\");\n});\n\nfn capturePackets(device: [*:0]const u8) !void {\n var errbuf: [c.PCAP_ERRBUF_SIZE]u8 = undefined;\n\n const handle = c.pcap_open_live(\n device,\n 65535, // snaplen\n 1, // promiscuo\n 1000, // timeout ms\n &errbuf,\n ) orelse return error.PcapOpenFailed;\n\n defer c.pcap_close(handle);\n\n // Setear filtro BPF para tráfico DNS\n var filter: c.struct_bpf_program = undefined;\n if (c.pcap_compile(handle, &filter, \"udp port 53\", 1, 0) == -1) {\n return error.FilterCompileFailed;\n }\n defer c.pcap_freecode(&filter);\n\n if (c.pcap_setfilter(handle, &filter) == -1) {\n return error.FilterSetFailed;\n }\n\n // Loop de captura\n while (true) {\n var header: *c.struct_pcap_pkthdr = undefined;\n var data: [*]const u8 = undefined;\n\n const result = c.pcap_next_ex(handle, &header, &data);\n if (result < 0) break;\n if (result == 0) continue; // timeout\n\n const packet = data[0..header.caplen];\n processPacket(packet) catch |err| {\n std.log.warn(\"Error parseando paquete: {}\", .{err});\n };\n }\n}\n```\n\nEsto llama funciones de libpcap directamente. Zig parsea los headers C en compile time (usando su parser C built-in, no libclang), resuelve los tipos y genera las llamadas ABI correctas.\n\nUn benchmark de una implementación de parser DNS revela la siguiente comparación:\n\n| Métrica | C (gcc -O2) | Zig (ReleaseFast) |\n| --- | --- | --- |\n| Tamaño binario | 47 KB | 52 KB |\n| Parsear 1M paquetes | 1.82s | 1.79s |\n| Memoria (RSS) | 2.1 MB | 2.3 MB |\n| Líneas de código | 1,847 | 1,203 |\n\nPerformance prácticamente idéntica, pero 35% menos código. El tamaño extra del binario viene del panic handler de Zig y soporte de stack trace (que se puede eliminar con ` --strip `).\n\n## Donde Zig se Queda Corto\n\nUna evaluación honesta requiere reconocer las limitaciones actuales:\n\n**El ecosistema es inmaduro.** El manejo de paquetes es built-in (` build.zig.zon `), pero el ecosistema de paquetes es escaso. ¿Se necesita un parser JSON? Hay tres, todos con estilos de API distintos y niveles de madurez diferentes. ¿Un cliente HTTP? La librería estándar de Zig tiene uno, pero todavía está verde. En C, hay décadas de librerías battle-tested.\n\n**Tiempos de compilación.** El uso intensivo de comptime puede hacer lenta la compilación. Un parser DNS con validación comptime agresiva tarda 4.2 segundos en compilar. El código C equivalente compila en 0.3 segundos. El compilador de Zig es single-threaded (el compilador self-hosted está mejorando esto, pero no llegó todavía).\n\n**Documentación.** La documentación oficial es una referencia del lenguaje, no un tutorial. Desarrolladores que vienen de C pueden pasar tiempo significativo entendiendo cómo funcionan los allocators, cómo manejar slices correctamente (no son punteros), y cómo funciona ` async ` (se está removiendo en 0.14, lo que indica el estado de estabilidad del lenguaje).\n\n**Churn pre-1.0.** Upgradeando de 0.12 a 0.13 puede requerir cambiar 40+ líneas en un codebase. Firmas de funciones cambian, APIs de la librería estándar se mueven, y algunas features comptime se retrabajan. Es esperable para un lenguaje pre-1.0, pero es un costo real.\n\n## El Caso para un Optimismo Cauteloso\n\nA pesar de estas limitaciones, Zig presenta una opción convincente para nuevos proyectos de sistemas, como agregadores de logs custom que necesitan parsear millones de líneas de log estructuradas por segundo. Varios factores soportan esta evaluación:\n\n1. **Comptime elimina clases enteras de bugs de runtime.** Validaciones que serían assertions en C (o panics en Rust) se convierten en errores de compilación en Zig.\n2. **La interop con C permite adopción incremental.** Se pueden llamar librerías C existentes desde el día uno y gradualmente reemplazarlas con implementaciones Zig.\n3. **El modelo de manejo de errores es mejor que el de C.** No tan comprehensivo como el ` Result ` de Rust, pero más ergonómico. La keyword ` try ` sola elimina docenas de bugs de \"olvidarse de chequear el código de retorno\".\n4. **Sin allocaciones ocultas, sin flujo de control oculto.** Cada alocación es explícita (se pasa un allocator), y no hay sobrecarga de operadores, conversiones implícitas, ni llamadas a funciones ocultas. Leer código Zig revela exactamente qué va a hacer la CPU.\n\nZig no va a reemplazar a Rust para sistemas memory-safe. No va a reemplazar a C en codebases legacy. Pero para código de sistemas nuevo donde se necesita performance nivel C, interop con C, y un lenguaje que respeta la inteligencia del desarrollador sin ahogarlo en teoría de tipos, es una de las opciones más sólidas disponibles. El comptime solo ya vale la curva de aprendizaje.\n\n## Preguntas frecuentes\n\n### ¿Zig está listo para producción?\n\nDepende de la definición de producción. Zig se usa en producción en Uber (para su toolchain Hermetic CC++), TigerBeetle (base de datos financiera), y Bun (runtime JavaScript). El lenguaje es pre-1.0 y todavía hay breaking changes entre releases, pero el compilador y la librería estándar son notablemente estables.\n\n### ¿Cómo difiere el comptime de Zig del constexpr de C++?\n\nC++ constexpr restringe qué operaciones se pueden hacer en compile time. El comptime de Zig no tiene tales restricciones, permite alocar memoria, hacer I/O (sobre datos de compile time), usar loops, condicionales y llamar cualquier función. El lenguaje completo está disponible en compile time, no un subset restringido.\n\n### ¿Debería aprender Zig o Rust para programación de sistemas?\n\nResuelven problemas distintos. Rust ofrece garantías de memory safety en compile time a través de su sistema de ownership, al costo de una curva de aprendizaje empinada y anotaciones de lifetime complejas. Zig ofrece manejo manual de memoria con mejor ergonomía que C, manejo de errores explícito, y metaprogramación comptime. Para memory safety demostrable, Rust es la opción más sólida. Para interop con C, simplicidad y poder de compile time, Zig vale la pena evaluar.\n\n## Tips\n\n1. **Tip:** Usar ` @compileLog() ` durante el desarrollo permite inspeccionar valores comptime, imprime en compile time e intencionalmente causa un error de compilación para que no quede en código de producción. Funciona como ` printf ` debugging para el compilador.\n2. **Tip:** Al llamar C desde Zig, conviene usar ` @cImport ` con ` @cInclude ` en vez de declaraciones extern manuales. El parser de headers C de Zig maneja la mayoría de los headers del sistema, incluyendo ramas ` #ifdef ` específicas de plataforma.\n3. **Tip:** La interfaz ` std.mem.Allocator ` de Zig permite intercambiar allocators en los call sites. Usar ` std.testing.allocator ` en tests habilita la detección automática de leaks y double-frees.\n4. **Tip:** Si el uso intensivo de comptime causa tiempos de compilación lentos, separar la lógica comptime en archivos aparte y usar ` @import ` ayuda. Zig cachea imports, así que el código comptime compartido corre una sola vez.\n\n- zig\n- comptime\n- programación-de-sistemas\n- interop-c\n- seguridad-de-memoria\n- compile-time\n- bajo-nivel\n- infraestructura\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)","headings":[{"level":1,"text":"El Comptime de Zig: Un Enfoque Unificado para la Programación de Sistemas en Compile-Time","url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#title"},{"level":2,"text":"Qué Es Realmente Comptime","url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#qu-es-realmente-comptime"},{"level":2,"text":"Reemplazando Generics con Comptime","url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#reemplazando-generics-con-comptime"},{"level":2,"text":"El Parser de Paquetes: Un Ejemplo Real","url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#el-parser-de-paquetes-un-ejemplo-real"},{"level":2,"text":"Manejo de Errores: La Feature Subestimada","url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#manejo-de-errores-la-feature-subestimada"},{"level":2,"text":"Interop con C: La Killer Feature Práctica","url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#interop-con-c-la-killer-feature-prctica"},{"level":2,"text":"Donde Zig se Queda Corto","url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#donde-zig-se-queda-corto"},{"level":2,"text":"El Caso para un Optimismo Cauteloso","url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#el-caso-para-un-optimismo-cauteloso"},{"level":2,"text":"Preguntas frecuentes","url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#questions"},{"level":3,"text":"¿Zig está listo para producción?","url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#question-1"},{"level":3,"text":"¿Cómo difiere el comptime de Zig del constexpr de C++?","url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#question-2"},{"level":3,"text":"¿Debería aprender Zig o Rust para programación de sistemas?","url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#question-3"},{"level":2,"text":"Tips","url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#article-tips"}],"sections":[{"heading":"El Comptime de Zig: Un Enfoque Unificado para la Programación de Sistemas en Compile-Time","level":1,"url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming","content":"Systems Programming"},{"heading":"El Comptime de Zig: Un Enfoque Unificado para la Programación de Sistemas en Compile-Time","level":1,"url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#title","content":"2026-02-0411 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/zig-language-comptime-system-programming>)"},{"heading":"Qué Es Realmente Comptime","level":2,"url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#qu-es-realmente-comptime","content":"La mayoría de los lenguajes tienen alguna forma de evaluación en compile time. C tiene macros ` #define ` y compilación condicional ` #ifdef `. C++ tiene ` constexpr ` y templates. Rust tiene ` const fn `, macros y generics. Cada uno de estos es un mecanismo separado con su propia sintaxis, reglas y limitaciones.\n\n[Zig](<https://ziglang.org/>) tiene un solo mecanismo: ` comptime `. Cualquier expresión puede evaluarse en compile time, y las mismas construcciones del lenguaje funcionan tanto en compile time como en runtime. No hay lenguaje de macros separado, no hay sintaxis de templates, no hay anotaciones de generics. Se escribe código Zig normal y se marcan partes como ` comptime `.\n\n```zig\n// Esta es una función regular que puede correr en compile time O runtime\nfn fibonacci(n: u64) u64 {\n if (n <= 1) return n;\n return fibonacci(n - 1) + fibonacci(n - 2);\n}\n\n// Evaluación en compile time\nconst fib_20 = comptime fibonacci(20); // Computado en compile time: 6765\n\n// Evaluación en runtime\nfn getfib(n: u64) u64 {\n return fibonacci(n); // Computado en runtime\n}\n```\n\nEsto se ve simple, tal vez hasta trivial. Pero las implicaciones se propagan en cascada. Un ejemplo real ilustra el punto."},{"heading":"Reemplazando Generics con Comptime","level":2,"url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#reemplazando-generics-con-comptime","content":"En C, crear una estructura de datos genérica requiere void pointers y macros:\n\n```c\n// C: Array dinámico genérico via macros\n#define DEFINE_VEC(T) \\\n typedef struct { \\\n T* items; \\\n size_t len; \\\n size_t cap; \\\n } Vec_##T; \\\n \\\n void vec_##T##_push(Vec_##T* v, T item) { \\\n if (v->len >= v->cap) { \\\n v->cap = v->cap ? v->cap * 2 : 8; \\\n v->items = realloc(v->items, sizeof(T) * v->cap); \\\n } \\\n v->items[v->len++] = item; \\\n }\n\nDEFINE_VEC(int)\nDEFINE_VEC(float)\n// Genera Vec_int, Vec_float, etc.\n```\n\nEsto es ilegible, imposible de debuggear, y produce mensajes de error espantosos. En Zig, el mismo concepto usa tipos comptime:\n\n```zig\nfn ArrayList(comptime T: type) type {\n return struct {\n items: []T,\n len: usize,\n capacity: usize,\n allocator: std.mem.Allocator,\n\n const Self = @This();\n\n pub fn init(allocator: std.mem.Allocator) Self {\n return Self{\n .items = &[_]T{},\n .len = 0,\n .capacity = 0,\n .allocator = allocator,\n };\n }\n\n pub fn append(self: *Self, item: T) !void {\n if (self.len >= self.capacity) {\n const new_cap = if (self.capacity == 0) 8 else self.capacity * 2;\n self.items = try self.allocator.realloc(self.items, new_cap);\n self.capacity = new_cap;\n }\n self.items[self.len] = item;\n self.len += 1;\n }\n\n pub fn deinit(self: *Self) void {\n self.allocator.free(self.items);\n }\n };\n}\n\n// Uso, el compilador genera un struct especializado para cada tipo\nvar list = ArrayList(u32).init(allocator);\ndefer list.deinit();\ntry list.append(42);\n```\n\nEl parámetro ` comptime T: type ` significa que ` T ` debe ser conocido en compile time. La función devuelve un ` type `, literalmente genera una definición de struct. Esto no es una instanciación de template; es código Zig real ejecutándose que produce un tipo. El cuerpo puede contener sentencias ` if `, loops, o cualquier otra construcción:\n\n```zig\nfn Matrix(comptime T: type, comptime rows: usize, comptime cols: usize) type {\n // Validación en compile time\n if (@typeInfo(T) != .Float and @typeInfo(T) != .Int) {\n @compileError(\"Matrix requiere un tipo numérico, recibió \" ++ @typeName(T));\n }\n\n if (rows == 0 or cols == 0) {\n @compileError(\"Las dimensiones de la matrix deben ser non-zero\");\n }\n\n return struct {\n data: [rows][cols]T,\n\n const Self = @This();\n\n // Generar multiplicación optimizada basada en dimensiones\n pub fn multiply(self: Self, other: Matrix(T, cols, comptime other_cols: usize)) Matrix(T, rows, other_cols) {\n var result: Matrix(T, rows, other_cols) = undefined;\n inline for (0..rows) |i| {\n inline for (0..other_cols) |j| {\n var sum: T = 0;\n inline for (0..cols) |k| {\n sum += self.data[i][k] * other.data[k][j];\n }\n result.data[i][j] = sum;\n }\n }\n return result;\n }\n };\n}\n```\n\nEl ` inline for ` desenrolla los loops en compile time. Para una multiplicación de matrices 4x4, el compilador genera 64 instrucciones multiply-add sin overhead de loop. Esto es el tipo de optimización que requeriría intrinsics o assembly escrito a mano en C."},{"heading":"El Parser de Paquetes: Un Ejemplo Real","level":2,"url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#el-parser-de-paquetes-un-ejemplo-real","content":"Una demostración convincente del poder de comptime surge al construir un parser de paquetes DNS. Una implementación C típica involucra aritmética de punteros, bounds checking manual, y lógica condicional para distintos tipos de registros, código notoriamente frágil y difícil de mantener.\n\nLa versión Zig usa comptime para generar parsers para distintos tipos de registros DNS:\n\n```zig\nconst DnsRecordType = enum(u16) {\n A = 1,\n AAAA = 28,\n CNAME = 5,\n MX = 15,\n TXT = 16,\n SRV = 33,\n};\n\nfn RecordData(comptime rtype: DnsRecordType) type {\n return switch (rtype) {\n .A => struct { addr: [4]u8 },\n .AAAA => struct { addr: [16]u8 },\n .CNAME => struct { name: []const u8 },\n .MX => struct { priority: u16, exchange: []const u8 },\n .TXT => struct { texts: []const []const u8 },\n .SRV => struct {\n priority: u16,\n weight: u16,\n port: u16,\n target: []const u8,\n },\n };\n}\n\nfn parseRecord(comptime rtype: DnsRecordType, data: []const u8) !RecordData(rtype) {\n var result: RecordData(rtype) = undefined;\n\n switch (rtype) {\n .A => {\n if (data.len < 4) return error.TruncatedPacket;\n result.addr = data[0..4].*;\n },\n .AAAA => {\n if (data.len < 16) return error.TruncatedPacket;\n result.addr = data[0..16].*;\n },\n .MX => {\n if (data.len < 2) return error.TruncatedPacket;\n result.priority = std.mem.readInt(u16, data[0..2], .big);\n result.exchange = try decompressName(data[2..]);\n },\n // ... otros tipos\n }\n\n return result;\n}\n```\n\nEn compile time, si se llama ` parseRecord(.A, some_data) `, el compilador genera una función que solo maneja registros A, las ramas del switch para AAAA, MX, etc. se eliminan completamente. Cero overhead en runtime para el dispatch de tipos.\n\nPero el poder real es la validación en compile time:\n\n```zig\ncomptime {\n // Verificar en compile time que todos los tipos de registro tienen parsers\n inline for (std.meta.fields(DnsRecordType)) |field| {\n const rtype: DnsRecordType = @enumFromInt(field.value);\n // Esto fuerza al compilador a instanciar el parser\n // para cada tipo de registro, atrapando errores en compile time\n _ = RecordData(rtype);\n }\n}\n```\n\nSi se agrega un nuevo tipo de registro al enum pero falta el case correspondiente en el switch de ` RecordData `, el código no compila. En C, eso sería un crash en runtime con el primer paquete del nuevo tipo de registro, probablemente a las 3 AM."},{"heading":"Manejo de Errores: La Feature Subestimada","level":2,"url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#manejo-de-errores-la-feature-subestimada","content":"Las discusiones sobre Zig tienden a enfocarse en comptime, pero el manejo de errores es igual de importante. Zig usa error unions, un valor es o el tipo esperado o un error:\n\n```zig\nconst ParseError = error{\n TruncatedPacket,\n InvalidHeader,\n LabelTooLong,\n CompressionLoop,\n BufferOverflow,\n};\n\nfn parseDnsMessage(buf: []const u8) ParseError!DnsMessage {\n if (buf.len < 12) return error.TruncatedPacket;\n\n const header = parseHeader(buf[0..12]);\n\n var offset: usize = 12;\n var questions: [MAX_QUESTIONS]Question = undefined;\n\n for (0..header.qdcount) |i| {\n // La keyword `try` propaga errores automáticamente\n questions[i] = try parseQuestion(buf, &offset);\n }\n\n return DnsMessage{\n .header = header,\n .questions = questions[0..header.qdcount],\n };\n}\n```\n\nEl ` ! ` en el tipo de retorno significa \"esta función puede fallar.\" La keyword ` try ` es equivalente a \"si esto devuelve un error, devolvelo de la función actual.\" Sin excepciones, sin códigos de error que se puedan olvidar de chequear, sin estado global ` errno `.\n\nComparación con el equivalente en C:\n\n```c\n// C: Cada caller tiene que acordarse de chequear el valor de retorno\n// Nada impide ignorar errores\nint parse_dns_message(const uint8_t* buf, size_t len, dns_message_t* out) {\n if (len < 12) return DNS_ERR_TRUNCATED;\n\n int err = parse_header(buf, &out->header);\n if (err) return err; // Fácil olvidarse de esta línea\n\n size_t offset = 12;\n for (int i = 0; i < out->header.qdcount; i++) {\n err = parse_question(buf, len, &offset, &out->questions[i]);\n if (err) return err; // Y de esta también\n }\n\n return 0;\n}\n```\n\nEn la práctica, los codebases C frecuentemente contienen retornos de error sin chequear, una categoría de bug que Zig elimina estructuralmente. Si una función devuelve un error union, debe manejarse."},{"heading":"Interop con C: La Killer Feature Práctica","level":2,"url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#interop-con-c-la-killer-feature-prctica","content":"Zig puede importar headers C directamente. No a través de FFI bindings, no a través de un paso de generación, directamente:\n\n```zig\nconst c = @cImport({\n @cInclude(\"pcap/pcap.h\");\n @cInclude(\"netinet/ip.h\");\n @cInclude(\"netinet/udp.h\");\n});\n\nfn capturePackets(device: [*:0]const u8) !void {\n var errbuf: [c.PCAP_ERRBUF_SIZE]u8 = undefined;\n\n const handle = c.pcap_open_live(\n device,\n 65535, // snaplen\n 1, // promiscuo\n 1000, // timeout ms\n &errbuf,\n ) orelse return error.PcapOpenFailed;\n\n defer c.pcap_close(handle);\n\n // Setear filtro BPF para tráfico DNS\n var filter: c.struct_bpf_program = undefined;\n if (c.pcap_compile(handle, &filter, \"udp port 53\", 1, 0) == -1) {\n return error.FilterCompileFailed;\n }\n defer c.pcap_freecode(&filter);\n\n if (c.pcap_setfilter(handle, &filter) == -1) {\n return error.FilterSetFailed;\n }\n\n // Loop de captura\n while (true) {\n var header: *c.struct_pcap_pkthdr = undefined;\n var data: [*]const u8 = undefined;\n\n const result = c.pcap_next_ex(handle, &header, &data);\n if (result < 0) break;\n if (result == 0) continue; // timeout\n\n const packet = data[0..header.caplen];\n processPacket(packet) catch |err| {\n std.log.warn(\"Error parseando paquete: {}\", .{err});\n };\n }\n}\n```\n\nEsto llama funciones de libpcap directamente. Zig parsea los headers C en compile time (usando su parser C built-in, no libclang), resuelve los tipos y genera las llamadas ABI correctas.\n\nUn benchmark de una implementación de parser DNS revela la siguiente comparación:\n\n| Métrica | C (gcc -O2) | Zig (ReleaseFast) |\n| --- | --- | --- |\n| Tamaño binario | 47 KB | 52 KB |\n| Parsear 1M paquetes | 1.82s | 1.79s |\n| Memoria (RSS) | 2.1 MB | 2.3 MB |\n| Líneas de código | 1,847 | 1,203 |\n\nPerformance prácticamente idéntica, pero 35% menos código. El tamaño extra del binario viene del panic handler de Zig y soporte de stack trace (que se puede eliminar con ` --strip `)."},{"heading":"Donde Zig se Queda Corto","level":2,"url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#donde-zig-se-queda-corto","content":"Una evaluación honesta requiere reconocer las limitaciones actuales:\n\n**El ecosistema es inmaduro.** El manejo de paquetes es built-in (` build.zig.zon `), pero el ecosistema de paquetes es escaso. ¿Se necesita un parser JSON? Hay tres, todos con estilos de API distintos y niveles de madurez diferentes. ¿Un cliente HTTP? La librería estándar de Zig tiene uno, pero todavía está verde. En C, hay décadas de librerías battle-tested.\n\n**Tiempos de compilación.** El uso intensivo de comptime puede hacer lenta la compilación. Un parser DNS con validación comptime agresiva tarda 4.2 segundos en compilar. El código C equivalente compila en 0.3 segundos. El compilador de Zig es single-threaded (el compilador self-hosted está mejorando esto, pero no llegó todavía).\n\n**Documentación.** La documentación oficial es una referencia del lenguaje, no un tutorial. Desarrolladores que vienen de C pueden pasar tiempo significativo entendiendo cómo funcionan los allocators, cómo manejar slices correctamente (no son punteros), y cómo funciona ` async ` (se está removiendo en 0.14, lo que indica el estado de estabilidad del lenguaje).\n\n**Churn pre-1.0.** Upgradeando de 0.12 a 0.13 puede requerir cambiar 40+ líneas en un codebase. Firmas de funciones cambian, APIs de la librería estándar se mueven, y algunas features comptime se retrabajan. Es esperable para un lenguaje pre-1.0, pero es un costo real."},{"heading":"El Caso para un Optimismo Cauteloso","level":2,"url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#el-caso-para-un-optimismo-cauteloso","content":"A pesar de estas limitaciones, Zig presenta una opción convincente para nuevos proyectos de sistemas, como agregadores de logs custom que necesitan parsear millones de líneas de log estructuradas por segundo. Varios factores soportan esta evaluación:\n\n1. **Comptime elimina clases enteras de bugs de runtime.** Validaciones que serían assertions en C (o panics en Rust) se convierten en errores de compilación en Zig.\n2. **La interop con C permite adopción incremental.** Se pueden llamar librerías C existentes desde el día uno y gradualmente reemplazarlas con implementaciones Zig.\n3. **El modelo de manejo de errores es mejor que el de C.** No tan comprehensivo como el ` Result ` de Rust, pero más ergonómico. La keyword ` try ` sola elimina docenas de bugs de \"olvidarse de chequear el código de retorno\".\n4. **Sin allocaciones ocultas, sin flujo de control oculto.** Cada alocación es explícita (se pasa un allocator), y no hay sobrecarga de operadores, conversiones implícitas, ni llamadas a funciones ocultas. Leer código Zig revela exactamente qué va a hacer la CPU.\n\nZig no va a reemplazar a Rust para sistemas memory-safe. No va a reemplazar a C en codebases legacy. Pero para código de sistemas nuevo donde se necesita performance nivel C, interop con C, y un lenguaje que respeta la inteligencia del desarrollador sin ahogarlo en teoría de tipos, es una de las opciones más sólidas disponibles. El comptime solo ya vale la curva de aprendizaje."},{"heading":"Preguntas frecuentes","level":2,"url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#questions","content":""},{"heading":"¿Zig está listo para producción?","level":3,"url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#question-1","content":"Depende de la definición de producción. Zig se usa en producción en Uber (para su toolchain Hermetic CC++), TigerBeetle (base de datos financiera), y Bun (runtime JavaScript). El lenguaje es pre-1.0 y todavía hay breaking changes entre releases, pero el compilador y la librería estándar son notablemente estables."},{"heading":"¿Cómo difiere el comptime de Zig del constexpr de C++?","level":3,"url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#question-2","content":"C++ constexpr restringe qué operaciones se pueden hacer en compile time. El comptime de Zig no tiene tales restricciones, permite alocar memoria, hacer I/O (sobre datos de compile time), usar loops, condicionales y llamar cualquier función. El lenguaje completo está disponible en compile time, no un subset restringido."},{"heading":"¿Debería aprender Zig o Rust para programación de sistemas?","level":3,"url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#question-3","content":"Resuelven problemas distintos. Rust ofrece garantías de memory safety en compile time a través de su sistema de ownership, al costo de una curva de aprendizaje empinada y anotaciones de lifetime complejas. Zig ofrece manejo manual de memoria con mejor ergonomía que C, manejo de errores explícito, y metaprogramación comptime. Para memory safety demostrable, Rust es la opción más sólida. Para interop con C, simplicidad y poder de compile time, Zig vale la pena evaluar."},{"heading":"Tips","level":2,"url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#article-tips","content":"1. **Tip:** Usar ` @compileLog() ` durante el desarrollo permite inspeccionar valores comptime, imprime en compile time e intencionalmente causa un error de compilación para que no quede en código de producción. Funciona como ` printf ` debugging para el compilador.\n2. **Tip:** Al llamar C desde Zig, conviene usar ` @cImport ` con ` @cInclude ` en vez de declaraciones extern manuales. El parser de headers C de Zig maneja la mayoría de los headers del sistema, incluyendo ramas ` #ifdef ` específicas de plataforma.\n3. **Tip:** La interfaz ` std.mem.Allocator ` de Zig permite intercambiar allocators en los call sites. Usar ` std.testing.allocator ` en tests habilita la detección automática de leaks y double-frees.\n4. **Tip:** Si el uso intensivo de comptime causa tiempos de compilación lentos, separar la lógica comptime en archivos aparte y usar ` @import ` ayuda. Zig cachea imports, así que el código comptime compartido corre una sola vez.\n\n- zig\n- comptime\n- programación-de-sistemas\n- interop-c\n- seguridad-de-memoria\n- compile-time\n- bajo-nivel\n- infraestructura\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)"}],"links":[{"text":"English","url":"https://lucioduran.com/blog/zig-language-comptime-system-programming"},{"text":"Zig","url":"https://ziglang.org/"},{"text":"Volver al Blog","url":"https://lucioduran.com/es/blog"}],"structuredData":[{"@context":"https://schema.org","@graph":[{"@type":"BlogPosting","@id":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming#article","mainEntityOfPage":{"@type":"WebPage","@id":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming"},"headline":"El Comptime de Zig: Un Enfoque Unificado para la Programación de Sistemas en Compile-Time","description":"La ejecución de código en compile-time en Zig reemplaza macros, generics y compilación condicional con un solo mecanismo unificado. Reconstruir un parser de paquetes en Zig revela cómo comptime desafía las suposiciones arraigadas en el mantenimiento tradicional de proyectos C.","datePublished":"2026-02-04","author":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"publisher":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"image":"https://lucioduran.com/favicons/og-default.png","url":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming","inLanguage":"es","isPartOf":{"@id":"https://lucioduran.com/blog#blog"},"articleSection":"Systems Programming","keywords":"zig, comptime, programación-de-sistemas, interop-c, seguridad-de-memoria, compile-time, bajo-nivel, infraestructura","wordCount":1970,"timeRequired":"PT11M","workTranslation":[{"@type":"BlogPosting","inLanguage":"en","url":"https://lucioduran.com/blog/zig-language-comptime-system-programming"}]},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Inicio","item":"https://lucioduran.com/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://lucioduran.com/es/blog"},{"@type":"ListItem","position":3,"name":"El Comptime de Zig: Un Enfoque Unificado para la Programación de Sistemas en Compile-Time","item":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming"}]},{"@type":"FAQPage","mainEntity":[{"@type":"Question","name":"¿Zig está listo para producción?","acceptedAnswer":{"@type":"Answer","text":"Depende de la definición de producción. Zig se usa en producción en Uber (para su toolchain Hermetic CC++), TigerBeetle (base de datos financiera), y Bun (runtime JavaScript). El lenguaje es pre-1.0 y todavía hay breaking changes entre releases, pero el compilador y la librería estándar son notablemente estables."}},{"@type":"Question","name":"¿Cómo difiere el comptime de Zig del constexpr de C++?","acceptedAnswer":{"@type":"Answer","text":"C++ constexpr restringe qué operaciones se pueden hacer en compile time. El comptime de Zig no tiene tales restricciones, permite alocar memoria, hacer I/O (sobre datos de compile time), usar loops, condicionales y llamar cualquier función. El lenguaje completo está disponible en compile time, no un subset restringido."}},{"@type":"Question","name":"¿Debería aprender Zig o Rust para programación de sistemas?","acceptedAnswer":{"@type":"Answer","text":"Resuelven problemas distintos. Rust ofrece garantías de memory safety en compile time a través de su sistema de ownership, al costo de una curva de aprendizaje empinada y anotaciones de lifetime complejas. Zig ofrece manejo manual de memoria con mejor ergonomía que C, manejo de errores explícito, y metaprogramación comptime. Para memory safety demostrable, Rust es la opción más sólida. Para interop con C, simplicidad y poder de compile time, Zig vale la pena evaluar."}}]}]}],"contentHash":"67146ac37935ca8f2207648cb77bd01ed44d67751dc926d57c4ab9b7fb12f3a9","provenance":{"method":"Deterministic serialization of the server-rendered main element","source":"https://lucioduran.com/es/blog/zig-language-comptime-system-programming","hashAlgorithm":"sha256","hashScope":"JSON document fields before contentHash and provenance","sourceHTMLBytes":72181,"contentBytes":16161}}
