Comptime e reflexão
Objetivo: rodar lógica em compilação, parametrizar tipos (generics), inspecionar tipos com
reflect(T), e firmar contratos comassert/assume/@requires.
Comptime: o núcleo puro rodando no compilador
Seção intitulada “Comptime: o núcleo puro rodando no compilador”comptime é o modificador “avalie isto em compilação”. Ele se aplica a binding, bloco, loop,
match e fn:
const SIZE := 64 // const = comptime letcomptime { assert (SIZE <= 1024) // checado na build}type é um valor de compilação: tipos são valores de primeira classe. Um generic é só uma
função que recebe/devolve type, executada pelo compilador.
Generics: [...] é compilação, (...) é runtime
Seção intitulada “Generics: [...] é compilação, (...) é runtime”decl List[T] { items: [*]T; len: usize; cap: usize } // açúcar de uma comptime fn que devolve type
fn repeat[n: int](s: string) -> string // n: compilação; s: runtimefn zeros[N: usize]() -> [N]int // N dimensiona o array em compilaçãoCada valor distinto em [...] gera uma função especializada, ou seja, monomorfização.
Constraints são declaradas, com régua estrita de símbolo: + significa implementa interface,
: é whitelist (pertence a um conjunto):
fn serialize[T + Serializable](v: T) -> []byte // capabilityfn encode[T: {i8, u16, bool}](v: T) -> []byte // whitelistfn proc[T: {i8, u16} + Serializable](v: T) -> []byte // combinaO erro estoura no boundary da instanciação, limpo, e não três camadas fundo como no duck typing do Zig.
Despacho: monomorfização vs vtable
Seção intitulada “Despacho: monomorfização vs vtable”Duas formas, escolha por necessidade:
T + Trait/ HKT gera monomorfização (estática; necessária quando o retorno depende deT, ex.map[A,B](xs: C[A]) -> C[B]).- valor de interface gera vtable (erased), capítulo 11.
Reflexão: reflect(T) (pacote compiler)
Seção intitulada “Reflexão: reflect(T) (pacote compiler)”reflect(T) expõe a estrutura de um tipo ao código comptime: é o que auto-deriva
serialize/hash sem você escrever nada. A forma do tipo é um variant-set, casado com comptime match exaustivo:
use compiler.{reflect, fail}
fn serialize[T](v: T, out: mut Buffer) { comptime match reflect(T).kind { Struct(s) => comptime loop f in s.fields { serialize(f.get(v), out) } // f.get lê o campo Tuple(parts) => comptime loop p in parts { serialize(p.get(v), out) } // posicional Enum(e) => { put_varint(out, e.tag(v)); /* + payload da variante ativa */ } Slice(_) => { put_varint(out, v.len); loop x in v { serialize(x, out) } } // length-prefix + N Array(_, _) => loop x in v { serialize(x, out) } // n vem do tipo Optional(_) => { out.write_byte(v.is_present() as byte); if v.is_present() { serialize(v.or_panic(), out) } } String => { put_varint(out, v.bytes.len); out.write(v.bytes) } // contagem + bytes UTF-8 Int(_) | Float(_) | Bool => out.write(v.to_bytes()) Ptr(_) | ManyPtr(_) | Channel(_) | Function(_) | RawPtr => fail("não serializa: endereço não cruza a fronteira") // fail, do compiler _ => fail("kind não serializável") // nada de valor cai aqui, todos os 10 kinds acima }}Na prática você nunca vê reflect: escreve [T + Serializable] e a derivação roda por baixo.
Quem desce a reflect(T) é autor de lib (encoding/json/hash).
Contratos: assert / assume / @requires
Seção intitulada “Contratos: assert / assume / @requires”A contenção de perigo (capítulo 13) tem dois níveis. Contrato de fronteira (@requires /
pós-condição) torna uma função segura de chamar: o compilador checa em cada chamada (estático
onde prova, panic onde não):
@requires(i < buf.len)fn at(buf: Buffer, i: usize) -> u8 == buf.bytes[i] // pós-condição tersa no tipo de retorno{ return unsafe raw_read(buf.ptr + i) assume "ptr válido: invariante de Buffer + bounds do @requires" }Abate de operação no corpo: assert (cond) checa (panic se falsa); assume "razão" confia
(sem check, sem licença pra UB). Duas keywords, uma intenção cada.
Próximo: 11 · Interfaces