TypeScript для системного программирования: неожиданный союз

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

TypeScript для системного программирования: когда JavaScript встречает железо

Когда мы говорим «системное программирование», в голову обычно приходят C, Rust, а может быть, Go или Zig. Но TypeScript? Тот самый язык, который «просто компилируется в JavaScript»? Поверьте, это не шутка. TypeScript проникает туда, где раньше царили указатели, сырая память и ручное управление ресурсами. И делает это элегантно.

В этой статье мы разберем, как TypeScript можно применять для задач, традиционно считающихся «системными»: работа с памятью, взаимодействие с нативным кодом (FFI), асинхронный ввод-вывод на уровне ядра, написание драйверов и даже embedded-разработка. Мы не будем утверждать, что TypeScript заменит C — но покажем, где его сильные стороны дают фору классическим подходам.

Почему TypeScript вообще рассматривают для системного программирования?

Классический аргумент против TypeScript — это сборка мусора и виртуальная машина. Да, это так. Но современный Node.js (и Deno, и Bun) работает на движке V8, который по производительности вплотную приближается к компилируемым языкам во многих сценариях. А главное — TypeScript дает вам безопасность типов, богатую экосистему и скорость разработки, недоступную чистому C.

Для системного программирования на TypeScript ключевыми преимуществами становятся:

  • Строгая типизация — ловит ошибки на этапе компиляции, а не во время выполнения
  • Современный async/await — идеально для неблокирующего I/O
  • Богатые возможности для метапрограммирования (декораторы, дженерики, условные типы)
  • Экосистема npm — тысячи готовых модулей для работы с системными вызовами
  • Возможность писать на одном языке и фронтенд, и бэкенд, и системные утилиты

Конечно, это не означает, что вы напишете ядро ОС на TypeScript. Но для инструментов командной строки, сетевых демонов, утилит мониторинга и даже некоторых драйверов — это отличный выбор.

Работа с памятью в TypeScript: не так страшно, как кажется

Один из главных страхов системных программистов — потеря контроля над памятью. В TypeScript вы не можете напрямую выделять и освобождать память, как в C. Но вы можете использовать ArrayBuffer, DataView и TypedArray для работы с сырыми бинарными данными.

Вот реальный пример — чтение бинарного заголовка PNG-файла без сторонних библиотек:

function readPNGHeader(buffer: ArrayBuffer): { width: number, height: number } | null {
  const view = new DataView(buffer);
  
  // Проверяем сигнатуру PNG (8 байт)
  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;
  }

  // Читаем IHDR chunk (смещение 16 байт: 8 сигнатура + 4 длина + 4 тип)
  const width = view.getUint32(16, false);  // Big-endian
  const height = view.getUint32(20, false); // Big-endian

  return { width, height };
}

// Использование:
const fs = require('fs');
const data = fs.readFileSync('image.png');
const result = readPNGHeader(data.buffer);
console.log(result); // { width: 1920, height: 1080 }

Как видите, мы работаем с памятью практически на уровне C — используем смещения, порядок байтов, сырые буферы. При этом TypeScript проверяет типы и защищает от глупых ошибок вроде передачи строки вместо числа.

FFI (Foreign Function Interface) — мост в нативный код

Когда производительности TypeScript не хватает, на помощь приходит FFI. С помощью модуля ffi-napi (для Node.js) вы можете вызывать функции из нативных библиотек (.so, .dll, .dylib) напрямую.

Пример — вызов функции getpid() из libc на Linux:

import ffi from 'ffi-napi';

const libc = ffi.Library(null, {
  'getpid': ['int', []]
});

console.log(`PID процесса: ${libc.getpid()}`);

А вот пример посложнее — работа с системным вызовом stat() для получения информации о файле:

import ffi from 'ffi-napi';
import ref from 'ref-napi';

// Определяем структуру stat (упрощенно)
const StatStruct = ref.defineStruct('stat', [
  ['dev_t', 'uint64'],
  ['ino_t', 'uint64'],
  ['mode_t', 'uint32'],
  ['nlink_t', 'uint32'],
  ['uid_t', 'uint32'],
  ['gid_t', 'uint32'],
  ['size', 'int64'],
  // ... остальные поля
]);

const libc = ffi.Library(null, {
  'stat': ['int', ['string', ref.refType(StatStruct)]]
});

const statBuf = new StatStruct();
const result = libc.stat('/etc/passwd', statBuf.ref());

if (result === 0) {
  console.log(`Размер файла: ${statBuf.size} байт`);
  console.log(`UID владельца: ${statBuf.uid_t}`);
}

Такой подход позволяет писать системные утилиты на TypeScript, которые работают с «железом» и ядром ОС, сохраняя при этом безопасность типов и скорость разработки.

Асинхронный I/O на уровне ядра

Одна из сильнейших сторон TypeScript (и JavaScript вообще) — это неблокирующий асинхронный I/O. В системном программировании это критично, когда нужно работать с сетью, файлами или устройствами без блокировки основного потока.

Современный Node.js использует libuv — кроссплатформенную библиотеку, которая абстрагирует системные вызовы epoll (Linux), kqueue (macOS) и IOCP (Windows). Это значит, что ваш TypeScript-код может эффективно обрабатывать тысячи одновременных соединений, не создавая потоков.

Пример — асинхронное чтение большого файла с контролем прогресса:

import { createReadStream } from 'fs';
import { createHash } from 'crypto';

async function hashFileWithProgress(filePath: string): Promise<string> {
  return new Promise((resolve, reject) => {
    const hash = createHash('sha256');
    const stream = createReadStream(filePath);
    const totalSize = fs.statSync(filePath).size;
    let processed = 0;

    stream.on('data', (chunk: Buffer) => {
      processed += chunk.length;
      const percent = ((processed / totalSize) * 100).toFixed(1);
      process.stdout.write(`\rПрогресс: ${percent}%`);
      hash.update(chunk);
    });

    stream.on('end', () => {
      console.log('\nГотово!');
      resolve(hash.digest('hex'));
    });

    stream.on('error', reject);
  });
}

// Использование
hashFileWithProgress('/path/to/large/file.iso')
  .then(hash => console.log(`                

Блоги

Рекомендаций по книгам