Prelude e built-ins
Todo programa Glide começa com um punhado de nomes já em escopo. Esses são o prelude: a família de macros println!, as primitivas brutas de heap (malloc/calloc/free/sizeof), construção de canais, acessadores de argumentos da CLI, os hooks do escalonador cooperativo, as macros de pânico/assert, introspecção de memória do processo e o tipo genérico `Pair<K, V>`.
Dois tipos de built-in vivem aqui, e a distinção importa:
- Built-ins de codegen — declarados como stubs
extern fn/macrocujos corpos o compilador reduz diretamente a C em cada ponto de chamada (sem implementação em Glide).malloc,make_chan,args_at,printf,sleep_ms, etc. O stub existe apenas para que hover/goto/completion sejam resolvidos e para que o tipador não sinalize a chamada de runtime implícita. - Macros —
println!,print!,format!,panic!,assert!,todo!,unimplemented!,unreachable!. Estas se expandem em tempo de compilação; as macros de diagnóstico reduzem a uma única chamada__glide_panic(msg, file, line)carregando a localização do código-fonte.
Alguns itens (mem_rss_mb, mem_rss_bytes, Pair::new) são pub fns comuns com corpos Glide reais — invólucros finos sobre os símbolos de runtime emitidos pelo codegen.
Importação
Nenhuma importação necessária — esses nomes estão sempre em escopo.
Superfície pública em resumo
| Item | Tipo | Assinatura |
|---|---|---|
println! |
macro | macro println!($($arg:expr),*) |
print! |
macro | macro print!($($arg:expr),*) |
format! |
macro | macro format!($($arg:expr),*) |
printf |
codegen extern fn |
extern fn printf(fmt: string, ...) -> i32 |
make_chan |
codegen extern fn |
extern fn make_chan(cap: i32) |
malloc |
codegen extern fn |
extern fn malloc(n: usize) -> *void |
calloc |
codegen extern fn |
extern fn calloc(count: usize, size: usize) -> *void |
free |
codegen extern fn |
extern fn free(p: *void) |
sizeof |
operador built-in | sizeof(T) -> usize |
args_count |
codegen extern fn |
extern fn args_count() -> i32 |
args_at |
codegen extern fn |
extern fn args_at(i: i32) -> string |
yield_now |
codegen extern fn |
extern fn yield_now() |
sleep_ms |
codegen extern fn |
extern fn sleep_ms(ms: i32) |
panic! |
macro | panic!(msg) |
assert! |
macro | assert!(cond) |
todo! |
macro | todo!(...) |
unimplemented! |
macro | unimplemented!() |
unreachable! |
macro | unreachable!() |
mem_rss_mb |
pub fn |
pub fn mem_rss_mb() -> i32 |
mem_rss_bytes |
pub fn |
pub fn mem_rss_bytes() -> i64 |
Pair<K, V> |
pub struct |
struct Pair<K, V> { pub first: K, pub second: V } |
Pair::new |
pub fn |
pub fn new(first: K, second: V) -> *Pair<K, V> |
__glide_panic |
pub extern fn |
extern fn __glide_panic(msg: string, file: string, line: i32) |
Os símbolos __glide_palloc* / __glide_proc_rss_* também são pub extern fn, mas são mecanismos internos do runtime — veja Símbolos internos do runtime.
Impressão e formatação
Todos os quatro são variádicos. println!/print!/format! são macros; printf é um codegen extern fn que repassa para a libc.
| Item | Assinatura | Descrição |
|---|---|---|
println! |
macro println!($($arg:expr),*) |
Imprime cada argumento separado por espaço, seguido de uma nova linha. A especificação de formato é inferida por argumento. |
print! |
macro print!($($arg:expr),*) |
Igual a println!, mas sem nova linha ao final. |
format! |
macro format!($($arg:expr),*) |
Interpola os marcadores {} no primeiro argumento literal com o restante; retorna uma string alocada no heap. |
printf |
extern fn printf(fmt: string, ...) -> i32 |
printf bruto da libc — especificadores de formato C, sem validação do lado Glide. Retorna a quantidade de bytes escritos. |
println! / print!
Os argumentos são separados por espaço e a especificação de formato é inferida pelo tipo de cada argumento, de modo que a maioria dos valores é impressa sem formatação manual. Aceitam zero ou mais argumentos de tipos variados (strings, i32/i64, floats, bools e qualquer coisa com representação imprimível).
fn main() -> i32 {
let name: string = "ada";
let age: i32 = 36;
println!("name:", name, "age:", age); // name: ada age: 36
// print! compõe uma linha lógica a partir de várias chamadas
print!("loading");
for let i: i32 = 0; i < 3; i++ {
print!(".");
}
println!(" done"); // loading... done
// argumentos mistos de bool + float
println!("flag:", age > 30, "ratio:", 3.5); // flag: true ratio: 3.5
return 0;
}
format!
Constrói uma string interpolando os marcadores {} no primeiro argumento literal com os argumentos restantes. Cada {} consome um argumento extra; o resultado é alocado no heap e pertence ao chamador. A especificação de formato para cada {} é inferida pelo tipo de seu argumento, exatamente como em println!.
fn main() -> i32 {
let n: i32 = 7;
let s: string = format!("count: {}, double: {}", n, n * 2);
println!(s); // count: 7, double: 14
// o resultado é uma string normal — armazene, passe, aninhe
let inner: string = format!("[{}]", n);
let outer: string = format!("wrapped {}", inner);
println!(outer); // wrapped [7]
return 0;
}
Qualquer literal de string contendo ${expr} é reduzida automaticamente a uma chamada format!, portanto as duas formas abaixo são equivalentes:
let a: string = format!("count: {}, double: {}", n, n * 2);
let b: string = "count: ${n}, double: ${n * 2}";
printf
A válvula de escape para comportamento genuíno de printf da C — largura, precisão, hexadecimal, notação científica. Retorna o número de bytes escritos (o valor de retorno da libc). A maior parte do código deve preferir print!/println!, que escolhem a especificação de formato por você.
fn main() -> i32 {
printf("hex: %08x\n", 255); // hex: 000000ff
printf("pi ~= %.4f\n", 3.14159); // pi ~= 3.1416
printf("%-10s | %5d\n", "items", 42); // items | 42
printf("%d + %d = %d\n", 2, 3, 5); // 2 + 3 = 5
let written: i32 = printf("%s\n", "hi"); // hi
println!("wrote", written, "bytes"); // wrote 3 bytes
return 0;
}
Canais
make_chan
extern fn make_chan(cap: i32);
Constrói um canal tipado. O tipo do elemento é inferido pela anotação do let; cap é o número de envios pendentes que o canal pode armazenar em buffer antes de send bloquear. Os canais são filas MPMC limitadas construídas sobre um anel de Vyukov com células preenchidas por cache, um caminho lento de pause-spin de 256 iterações e sinalização condicional com variável de condição — sem lock no caminho rápido, sem syscall quando não há waiters. Opere com c.send(x), c.recv(), c.close(), ou drene com while let v = c.recv() { … }.
fn main() -> i32 {
let c: chan<i32> = make_chan(8);
c.send(1);
c.send(2);
c.send(3);
c.close();
while let v = c.recv() {
println!("got", v); // got 1 / got 2 / got 3
}
return 0;
}
Um produtor rodando em outra tarefa alimenta o canal; o consumidor drena até close():
struct Job {
n: i32,
}
fn produce(c: chan<*Job>) {
for let i: i32 = 0; i < 3; i++ {
let j: *Job = malloc(sizeof(Job)) as *Job;
j.n = i;
c.send(j);
}
c.close();
}
fn main() -> i32 {
let c: chan<*Job> = make_chan(4);
spawn produce(c);
while let j = c.recv() {
println!("job", j.n); // job 0 / job 1 / job 2
free(j as *void);
}
return 0;
}
Alocação bruta no heap
Estas são válvulas de escape para a libc, reduzidas diretamente pelo codegen. Emparelhe cada alocação com um free correspondente (ou encapsule com defer free(p as *void);).
| Item | Assinatura | Descrição |
|---|---|---|
malloc |
extern fn malloc(n: usize) -> *void |
Aloca n bytes não inicializados. null em caso de OOM. |
calloc |
extern fn calloc(count: usize, size: usize) -> *void |
Aloca count * size bytes, zerados. |
free |
extern fn free(p: *void) |
Libera um ponteiro de malloc/calloc. null é um no-op; double-free é UB. |
sizeof |
operador built-in | Tamanho em bytes de um tipo, p. ex. sizeof(Item), sizeof(Pair<i32, i32>). |
O idioma cast/free
malloc/calloc retornam *void. O padrão canônico é: dimensionar a alocação com sizeof(T), converter o resultado para *T, trabalhar com ele e depois converter de volta para *void para o free.
struct Item {
id: i32,
score: i32,
}
fn main() -> i32 {
// objeto único: malloc(sizeof(T)) as *T
let one: *Item = malloc(sizeof(Item)) as *Item;
one.id = 5;
one.score = 99;
println!("item", one.id, one.score); // item 5 99
free(one as *void);
// array zerado: calloc(count, size) — campos começam em 0
let arr: *Item = calloc(10, sizeof(Item)) as *Item;
arr.id = 1;
println!("zeroed score:", arr.id, arr.score); // zeroed score: 1 0
free(arr as *void);
// buffer de bytes brutos + defer-free para não vazar em retorno antecipado
let buf: *u8 = malloc(64) as *u8;
defer free(buf as *void);
// sizeof funciona também em instanciações genéricas
println!("sizeof Item =", sizeof(Item));
println!("sizeof Pair<i32,i32> =", sizeof(Pair<i32, i32>));
return 0;
}
Argumentos da CLI
| Item | Assinatura | Descrição |
|---|---|---|
args_count |
extern fn args_count() -> i32 |
Número de argumentos da CLI (equivale ao argc de C). |
args_at |
extern fn args_at(i: i32) -> string |
Argumento no índice i (equivale a argv[i]). |
O índice 0 é o caminho do programa; os índices 1..args_count() são os argumentos do usuário. Um índice inexistente retorna "".
fn main() -> i32 {
let prog: string = args_at(0); // caminho para o binário
println!("program:", prog);
for let i: i32 = 1; i < args_count(); i++ {
println!("arg", i, "=", args_at(i));
}
return 0;
}
Auxiliares do escalonador
Estes cooperam com o escalonador M:N do Glide.
| Item | Assinatura | Descrição |
|---|---|---|
sleep_ms |
extern fn sleep_ms(ms: i32) |
Dorme pelo menos ms ms. Assíncrono dentro de uma corrotina (pausa na thread de timer); sleep bloqueante da libc fora de uma. |
yield_now |
extern fn yield_now() |
Yield cooperativo — recoloca a tarefa atual na fila e executa outra. No-op fora de uma corrotina. |
fn main() -> i32 {
for let i: i32 = 0; i < 3; i++ {
println!("tick", i);
yield_now(); // cede a vez para outras tarefas
sleep_ms(1); // pausa ~1ms
}
return 0;
}
sleep_ms dentro de uma corrotina pausa a coro na thread de timer do runtime, liberando o worker para executar outra tarefa pronta imediatamente. Fora de uma corrotina (p. ex., em main antes de qualquer spawn) cai de volta para o nanosleep bloqueante da libc / Sleep da WinAPI.
Macros de diagnóstico
Estas macros abortam o programa com uma mensagem e a localização no código-fonte. Todas reduzem a uma única chamada __glide_panic(msg, file, line) que o expansor preenche com o caminho e a linha do ponto de chamada.
| Macro | Uso |
|---|---|
panic!(msg) |
Aborta imediatamente com msg e a localização do ponto de chamada. |
assert!(cond) |
Aborta se cond for falso. |
todo!(...) |
Marca um ramo inacabado; aborta se alcançado. |
unimplemented!() |
Marca um caminho deliberadamente não implementado; aborta se alcançado. |
unreachable!() |
Asserta que um ramo nunca pode ser executado; aborta se for. |
fn classify(n: i32) -> string {
if n < 0 {
panic!("negative input");
}
if n == 0 {
return "zero";
}
return "positive";
}
fn main() -> i32 {
assert!(1 + 1 == 2);
println!(classify(5)); // positive
if false {
todo!("handle the other case");
}
if false {
unimplemented!();
}
if false {
unreachable!();
}
return 0;
}
assert! combina bem com funções que têm invariantes. Ele aborta (não retorna !T), portanto use-o para erros de programação, não para condições recuperáveis:
fn checked_div(a: i32, b: i32) -> i32 {
assert!(b != 0); // aborta em uso incorreto
return a / b;
}
fn main() -> !i32 {
println!("div:", checked_div(10, 2)); // div: 5
return ok(0);
}
Introspecção de memória
Tamanho do conjunto residente (RSS) do processo atual. Ambas retornam -1 em plataformas que não expõem RSS. São invólucros pub fn reais sobre as sondas de runtime específicas de cada plataforma.
| Item | Assinatura | Descrição |
|---|---|---|
mem_rss_mb |
pub fn mem_rss_mb() -> i32 |
RSS do processo em megabytes (1 MiB = 1024×1024). |
mem_rss_bytes |
pub fn mem_rss_bytes() -> i64 |
RSS do processo em bytes. Útil para cálculos por tarefa. |
fn main() -> i32 {
let before: i64 = mem_rss_bytes();
println!("rss MB:", mem_rss_mb());
println!("rss bytes:", before);
// realiza algum trabalho, depois mede o delta
let after: i64 = mem_rss_bytes();
println!("delta bytes:", after - before);
return 0;
}
mem_rss_bytes retorna i64, então converta contagens de tarefas para i64 em cálculos por tarefa: rss / (n as i64).
Esses são invólucros Glide finos sobre as sondas de runtime específicas de cada plataforma (GetProcessMemoryInfo no Windows, /proc/self/statm no Linux, Mach task_info no macOS). Em uma plataforma não suportada, ambos retornam -1 — verifique antes de dividir.
Pair<K, V>
A tupla mínima viável — Glide ainda não tem tuplas anônimas, então APIs que retornam dois valores usam essa struct. Notavelmente, HashMap::entries expõe pares chave/valor como um *Vector<Pair<string, V>> para que os chamadores possam iterar sem snapshots separados de keys()/values().
pub struct Pair<K, V> {
pub first: K,
pub second: V,
}
| Campo | Tipo | Descrição |
|---|---|---|
first |
K |
Primeiro elemento. |
second |
V |
Segundo elemento. |
Pair::new
pub fn new(first: K, second: V) -> *Pair<K, V>
Constrói um Pair alocado no heap. O chamador possui o resultado e é responsável por liberá-lo. Os dois parâmetros de tipo são independentes, então um Pair pode conter valores de tipos diferentes (Pair<i32, string>), e pode até ser aninhado (Pair<string, *Pair<i32, i32>>).
fn divmod(a: i32, b: i32) -> *Pair<i32, i32> {
let p: *Pair<i32, i32> = malloc(sizeof(Pair<i32, i32>)) as *Pair<i32, i32>;
p.first = a / b;
p.second = a % b;
return p;
}
fn main() -> i32 {
// Pair::new aloca no heap; K e V podem ser diferentes
let p: *Pair<i32, string> = Pair::new(42, "answer");
println!(p.first, p.second); // 42 answer
free(p as *void);
// campos são mutáveis no lugar
let q: *Pair<i32, i32> = divmod(17, 5);
println!("17 / 5 =", q.first, "rem", q.second); // 17 / 5 = 3 rem 2
q.first = q.first + 1;
println!("bumped:", q.first); // bumped: 4
free(q as *void);
// pares se aninham
let inner: *Pair<i32, i32> = Pair::new(1, 2);
let outer: *Pair<string, *Pair<i32, i32>> = Pair::new("pt", inner);
println!(outer.first, outer.second.first, outer.second.second); // pt 1 2
free(outer.second as *void);
free(outer as *void);
return 0;
}
Símbolos internos do runtime
O módulo de built-ins também exporta um conjunto de extern fns __glide_* que sustentam o alocador de arena por tecla usado pelo pipeline do LSP/compilador:
pub extern fn __glide_palloc(size: i32) -> *void;
pub extern fn __glide_palloc_make() -> *void;
pub extern fn __glide_palloc_free(handle: *void);
pub extern fn __glide_palloc_active() -> i32;
pub extern fn __glide_palloc_get() -> *void;
pub extern fn __glide_palloc_set(a: *void);
pub extern fn __glide_palloc_owns(p: *void) -> i32;
pub extern fn __glide_pfree(p: *void);
pub extern fn __glide_palloc_total_bytes() -> i32;
pub extern fn __glide_palloc_chunks() -> i32;
pub extern fn __glide_proc_rss_mb() -> i32;
pub extern fn __glide_proc_rss_bytes() -> i64;
pub extern fn __glide_panic(msg: string, file: string, line: i32);
__glide_palloc* implementa um alocador bump (arena): cada Stmt/Expr/Type/Vector/HashMap alocado enquanto uma arena está ativa vem de chunks encadeados pertencentes a essa arena, e toda a arena é descartada em bloco quando o pressionamento de tecla termina. Fora do caminho do LSP, a arena ativa é nula e __glide_palloc cai de volta para o calloc comum, de modo que build/run/fmt/check mantêm o comportamento normal de heap.