Confundir comptime vs runtime — Como Resolver em Zig
O Que Este Erro Significa
Um dos conceitos mais poderosos e ao mesmo tempo confusos de Zig para iniciantes é a distinção entre comptime (tempo de compilação) e runtime (tempo de execução). Muitos erros ocorrem quando o programador tenta usar valores de runtime em contextos que exigem comptime, ou vice-versa. O compilador reporta esses erros com mensagens como:
error: unable to evaluate comptime expression
error: expected comptime-known value
error: runtime value used in comptime context
Em Zig, comptime permite executar código durante a compilação, gerar tipos, otimizar caminhos de código e fazer metaprogramação — mas exige que os valores sejam conhecidos antes do programa rodar.
Causas Comuns
1. Usar Variável Runtime como Tipo
const std = @import("std");
pub fn main() void {
var tamanho: usize = 10;
_ = &tamanho;
// ERRO: tamanho não é comptime — não pode ser usado como tamanho de tipo
var array: [tamanho]u8 = undefined;
_ = array;
}
2. Valor Runtime em switch com comptime
const std = @import("std");
pub fn main() !void {
const stdin = std.io.getStdIn().reader();
var buf: [100]u8 = undefined;
const input = try stdin.readUntilDelimiterOrEof(&buf, '\n');
_ = input;
// input é runtime — não pode ser usado em padrões comptime
// const tipo = @TypeOf(input); // Isso funciona, mas...
}
3. Tentar Criar Tipo com Dados de Runtime
fn criarArray(tamanho: usize) void {
// ERRO: tamanho é runtime, mas [N]T requer N comptime
var arr: [tamanho]u8 = undefined;
_ = arr;
}
4. @compileLog em Código Runtime
const std = @import("std");
pub fn main() void {
var x: u32 = 42;
_ = &x;
@compileLog(x); // ERRO: x é runtime, @compileLog precisa de comptime
}
5. Usar inline for com Valores Runtime
pub fn main() void {
var n: usize = 5;
_ = &n;
// ERRO: inline for precisa de range comptime
inline for (0..n) |i| {
_ = i;
}
}
6. Retornar Tipo Diferente Baseado em Condição Runtime
fn processar(modo: bool) if (modo) u32 else i32 {
// ERRO: modo é runtime, mas o tipo de retorno precisa ser comptime
if (modo) return 42 else return -1;
}
Como Corrigir
Solucao 1: Marcar Parâmetro como comptime
fn criarArray(comptime tamanho: usize) [tamanho]u8 {
return [_]u8{0} ** tamanho; // OK: tamanho é comptime
}
pub fn main() void {
var arr = criarArray(10); // 10 é comptime — OK
arr[0] = 42;
}
Solucao 2: Usar Alocação Dinâmica para Tamanhos Runtime
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var tamanho: usize = 10;
_ = &tamanho;
// Para tamanhos runtime, use alocação dinâmica
const buffer = try allocator.alloc(u8, tamanho);
defer allocator.free(buffer);
buffer[0] = 42;
}
Solucao 3: Usar comptime para Geração de Tipos
fn VetorDe(comptime T: type, comptime N: usize) type {
return struct {
dados: [N]T,
const Self = @This();
fn init() Self {
return .{ .dados = [_]T{0} ** N };
}
fn get(self: *const Self, i: usize) T {
return self.dados[i];
}
};
}
pub fn main() void {
const Vec3 = VetorDe(f32, 3);
var v = Vec3.init();
v.dados[0] = 1.0;
v.dados[1] = 2.0;
v.dados[2] = 3.0;
_ = v;
}
Solucao 4: Separar Lógica Comptime e Runtime
const std = @import("std");
// Função comptime — gera código em tempo de compilação
fn calcularFibComptime(comptime n: u32) u32 {
if (n <= 1) return n;
return calcularFibComptime(n - 1) + calcularFibComptime(n - 2);
}
// Função runtime — executa em tempo de execução
fn calcularFibRuntime(n: u32) u32 {
if (n <= 1) return n;
var a: u32 = 0;
var b: u32 = 1;
var i: u32 = 2;
while (i <= n) : (i += 1) {
const temp = a + b;
a = b;
b = temp;
}
return b;
}
pub fn main() void {
// Comptime — resultado calculado durante compilação
const fib_10 = comptime calcularFibComptime(10);
std.debug.print("Fib(10) comptime: {}\n", .{fib_10});
// Runtime — resultado calculado durante execução
var n: u32 = 10;
_ = &n;
const fib_n = calcularFibRuntime(n);
std.debug.print("Fib({}) runtime: {}\n", .{ n, fib_n });
}
Solucao 5: Usar @as para Forçar Tipo Comptime
const std = @import("std");
pub fn main() void {
// Literal numérico é comptime
const tamanho = 100; // comptime_int
// @as converte para tipo concreto em comptime
const buffer_size = @as(usize, tamanho);
var buf: [buffer_size]u8 = undefined;
buf[0] = 42;
_ = buf;
}
Solucao 6: Bloco comptime para Computação em Compilação
const std = @import("std");
const tabela_lookup = comptime blk: {
var result: [256]u8 = undefined;
for (0..256) |i| {
result[i] = @intCast((i * 7 + 3) % 256);
}
break :blk result;
};
pub fn main() void {
// tabela_lookup é computada em tempo de compilação
// Nenhum custo de inicialização em runtime
std.debug.print("tabela[42] = {}\n", .{tabela_lookup[42]});
}
Comptime vs Runtime: Guia Rápido
| Contexto | Comptime | Runtime |
|---|---|---|
| Tamanhos de array | [N]T requer N comptime | Use alloc para runtime |
| Tipos | Tipos são SEMPRE comptime | Não existe tipo runtime |
| Parâmetros | comptime param: T | param: T |
| Condições | if (comptime ...) avaliado na compilação | if (...) avaliado na execução |
| Loops | inline for requer range comptime | for aceita runtime |
| Valores | const x = 42 (literal) é comptime | var x: u32 = 42 é runtime |
Quando Usar comptime
- Geração de tipos:
fn MeuTipo(comptime T: type) type - Tabelas de lookup:
const tabela = comptime { ... } - Otimização: unrolling de loops com
inline for - Validação:
@compileErrorpara verificar condições em compilação - Metaprogramação: gerar código baseado em tipos
Quando Usar Runtime
- Dados do usuário: input, argumentos de CLI, variáveis de ambiente
- I/O: leitura de arquivos, rede
- Alocação dinâmica: tamanhos variáveis em runtime
- Estado mutável: variáveis que mudam durante execução
Erros Relacionados
- Expected expression — Expressão esperada
- Type mismatch — Incompatibilidade de tipo
- Expected type — Tipo esperado