TypeScript para programação de sistemas: quando o JavaScript encontra o hardware
Quando falamos "programação de sistemas", geralmente pensamos em C, Rust, ou talvez Go ou Zig. Mas TypeScript? Aquela linguagem que "apenas compila para JavaScript"? Acredite, não é brincadeira. O TypeScript está penetrando onde antes reinavam ponteiros, memória bruta e gerenciamento manual de recursos. E faz isso de forma elegante.
Neste artigo, vamos explorar como o TypeScript pode ser aplicado a tarefas tradicionalmente consideradas "sistêmicas": manipulação de memória, interação com código nativo (FFI), entrada/saída assíncrona em nível de kernel, escrita de drivers e até desenvolvimento embarcado. Não vamos afirmar que o TypeScript substituirá o C — mas mostraremos onde seus pontos fortes superam as abordagens clássicas.
Por que o TypeScript é considerado para programação de sistemas?
O argumento clássico contra o TypeScript é a coleta de lixo e a máquina virtual. Sim, é verdade. Mas o Node.js moderno (e Deno, e Bun) roda no motor V8, cujo desempenho se aproxima de linguagens compiladas em muitos cenários. E o principal: o TypeScript oferece segurança de tipos, um ecossistema rico e velocidade de desenvolvimento inacessível ao C puro.
Para programação de sistemas com TypeScript, as principais vantagens são:
- Tipagem forte — detecta erros em tempo de compilação, não em execução
- Async/await moderno — ideal para I/O não bloqueante
- Amplas capacidades de metaprogramação (decoradores, genéricos, tipos condicionais)
- Ecossistema npm — milhares de módulos prontos para trabalhar com chamadas de sistema
- Possibilidade de escrever frontend, backend e utilitários de sistema na mesma linguagem
Claro, isso não significa que você escreverá um kernel de SO em TypeScript. Mas para ferramentas de linha de comando, daemons de rede, utilitários de monitoramento e até alguns drivers — é uma excelente escolha.
Manipulação de memória em TypeScript: não é tão assustador quanto parece
Um dos maiores medos dos programadores de sistemas é perder o controle sobre a memória. No TypeScript, você não pode alocar e liberar memória diretamente como em C. Mas pode usar ArrayBuffer, DataView e TypedArray para trabalhar com dados binários brutos.
Aqui está um exemplo real — leitura do cabeçalho binário de um arquivo PNG sem bibliotecas externas:
function readPNGHeader(buffer: ArrayBuffer): { width: number, height: number } | null { const view = new DataView(buffer); // Verifica a assinatura PNG (8 bytes) const signature = new Uint8Array(buffer, 0, 8); const pngSignature = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]); for (let i = 0; i < 8; i++) { if (signature[i] !== pngSignature[i]) return null; }
// Lê o chunk IHDR (offset 16 bytes: 8 assinatura + 4 comprimento + 4 tipo) const width = view.getUint32(16, false); // Big-endian const height = view.getUint32(20, false); // Big-endian
return { width, height };}
// Uso:const fs = require('fs');const data = fs.readFileSync('image.png');const result = readPNGHeader(data.buffer);console.log(result); // { width: 1920, height: 1080 }Como você pode ver, trabalhamos com memória quase no nível de C — usando offsets, ordem de bytes, buffers brutos. Enquanto isso, o TypeScript verifica tipos e protege contra erros bobos, como passar uma string em vez de um número.
FFI (Foreign Function Interface) — ponte para código nativo
Quando o desempenho do TypeScript não é suficiente, o FFI vem em socorro. Com o módulo ffi-napi (para Node.js), você pode chamar funções de bibliotecas nativas (.so, .dll, .dylib) diretamente.
Exemplo — chamando a função getpid() da libc no Linux:
import ffi from 'ffi-napi';
const libc