TypeScript para programación de sistemas: cuando JavaScript se encuentra con el hardware
Cuando hablamos de «programación de sistemas», normalmente vienen a la mente C, Rust, o quizás Go o Zig. ¿Pero TypeScript? ¿Ese mismo lenguaje que «simplemente se compila a JavaScript»? Créanme, no es una broma. TypeScript se está adentrando en territorios donde antes reinaban los punteros, la memoria cruda y la gestión manual de recursos. Y lo hace con elegancia.
En este artículo, analizaremos cómo se puede aplicar TypeScript a tareas tradicionalmente consideradas «de sistema»: gestión de memoria, interacción con código nativo (FFI), entrada/salida asíncrona a nivel de kernel, escritura de controladores e incluso desarrollo embebido. No afirmaremos que TypeScript reemplazará a C, pero mostraremos dónde sus puntos fuertes superan a los enfoques clásicos.
¿Por qué se considera siquiera TypeScript para la programación de sistemas?
El argumento clásico contra TypeScript es la recolección de basura y la máquina virtual. Sí, es cierto. Pero el Node.js moderno (y Deno, y Bun) funciona sobre el motor V8, cuyo rendimiento se acerca mucho al de los lenguajes compilados en muchos escenarios. Y lo principal: TypeScript te brinda seguridad de tipos, un ecosistema rico y una velocidad de desarrollo inalcanzable con C puro.
Para la programación de sistemas con TypeScript, las ventajas clave son:
- Tipado estricto: detecta errores en tiempo de compilación, no en tiempo de ejecución
- Async/await moderno: ideal para E/S no bloqueante
- Ricas capacidades de metaprogramación (decoradores, genéricos, tipos condicionales)
- Ecosistema npm: miles de módulos listos para trabajar con llamadas al sistema
- Posibilidad de escribir en un solo lenguaje tanto el frontend, como el backend y las utilidades del sistema
Por supuesto, esto no significa que escribirás el núcleo de un SO en TypeScript. Pero para herramientas de línea de comandos, demonios de red, utilidades de monitoreo e incluso algunos controladores, es una excelente opción.
Gestión de memoria en TypeScript: no es tan aterrador como parece
Uno de los mayores miedos de los programadores de sistemas es perder el control sobre la memoria. En TypeScript no puedes asignar y liberar memoria directamente como en C. Pero puedes usar ArrayBuffer, DataView y TypedArray para trabajar con datos binarios crudos.
Aquí hay un ejemplo real: leer la cabecera binaria de un archivo PNG sin bibliotecas externas:
function readPNGHeader(buffer: ArrayBuffer): { width: number, height: number } | null { const view = new DataView(buffer); // Verificamos la firma 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; }
// Leemos el chunk IHDR (desplazamiento 16 bytes: 8 firma + 4 longitud + 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 puedes ver, trabajamos con la memoria casi a nivel de C: usamos desplazamientos, orden de bytes, búferes crudos. Mientras tanto, TypeScript verifica los tipos y protege contra errores tontos como pasar una cadena en lugar de un número.
FFI (Foreign Function Interface) — puente hacia el código nativo
Cuando el rendimiento de TypeScript no es suficiente, el FFI viene al rescate. Usando el módulo ffi-napi (para Node.js) puedes llamar directamente a funciones de bibliotecas nativas (.so, .dll, .dylib).
Ejemplo: llamar a la función getpid() de libc en Linux:
import ffi from 'ffi-napi';
const libc