IO e async
Inspirado no Zig 0.17: async é desacoplado do sistema de tipos. O desenvolvedor escreve código linear, sem se preocupar se a operação é blocking ou non-blocking. A interface IO resolve isso internamente.
Sem “function coloring”: uma função que faz IO não força seu caller a ser marcado como async. O scheduler cuida da suspensão e da retomada de processos de forma transparente.
O problema que isso resolve
Seção intitulada “O problema que isso resolve”Em linguagens com function coloring (JavaScript, Python, Rust/Tokio), async infecta o call stack:
// Rust/JS: async vaza para cima infinitamenteasync fn read_file() -> String { ... }async fn process() -> Data { read_file().await } // forçadoasync fn handle() -> Response { process().await } // forçadoasync fn main() { handle().await } // forçadoAqui, nenhuma função é marcada. O scheduler suspende o processo quando IO bloqueia e o retoma quando o dado chega:
// Parece síncrono, é não-bloqueante por baixofn read_file(path: string) -> Result[string, error{NotFound}] { return io.read(path) // scheduler suspende este processo aqui se necessário}
fn process(path: string) -> Result[Data, error{...}] { content := read_file(path) catch |e| { return e } return parse(content)}
fn handle(req: Request) -> Response { data := process(req.path) catch |e| { return Response.error(e) } return Response.ok(data)}io.read é uma chamada normal de função. Internamente, ela é implementada via a interface IO do runtime, que pode usar epoll, io_uring ou kqueue, conforme o OS. O processo atual é suspenso pelo scheduler enquanto aguarda, liberando o core para outros processos. Quando o dado chega, o scheduler retoma de onde parou.
Paralelismo de IO via processos
Seção intitulada “Paralelismo de IO via processos”Quando você quer múltiplas operações de IO em paralelo, usa processos, não async/await nem Futures:
// IO paralelo: cada processo faz sua leitura independentementechan_a: Channel[->string] = ...chan_b: Channel[->string] = ...
spawn fetch_url("https://api-a.com", chan_a)spawn fetch_url("https://api-b.com", chan_b)
// Espera os dois, bloqueia só quando necessárioresult_a := -> chan_a catch |e| { return }result_b := -> chan_b catch |e| { return }Ou, se quiser o primeiro que chegar, com match sem sujeito (a forma “select”; seção 4):
match { data := -> chan_a => handle(data) data := -> chan_b => handle(data)} timeout(5s) { return error.Timeout}O que o runtime garante
Seção intitulada “O que o runtime garante”- Uma chamada
io.*nunca bloqueia o scheduler, só o processo que a fez (epoll ou io_uring por baixo). Uma chamada FFI bloqueante (seção 17) é outra história: o work-stealing a absorve num pool, e o determinístico exige que você a isole. Ver “FFI bloqueante e o scheduler” (seção 3). - O processo é suspenso cooperativamente no ponto exato do
io.*; entre instruções, o scheduler ainda pode preemptá-lo (seção 3). - Zero overhead de alocação de
FutureouTask, pois o processo já é a unidade de suspensão.