Nim — компилируемый язык со статической типизацией, который читается почти как Python: отступы вместо скобок, короткие объявления. Программа собирается в нативный бинарник через C, C++ или JavaScript, работает без виртуальной машины и часто обходится без сборщика мусора. Для администратора это один файл без зависимостей.

Что даёт Nim и где он уместен

Язык появился как замена скриптам, которым не хватает скорости, и C-программам, которым не хватает безопасности. В Nim 2.x по умолчанию используется управление памятью ORC: подсчёт ссылок с отдельным проходом по циклам. Это компромисс между простотой Python и предсказуемостью C.

Практические ниши:

  • консольные утилиты и парсеры с быстрым стартом;
  • сетевые демоны и небольшие HTTP-сервисы;
  • замена Python-скриптов в cron, где важны память и отсутствие интерпретатора;
  • генерация кода и DSL через макросы.

Слабое место — экосистема: библиотек меньше, чем у Python или Go. Если проект живёт за счёт готовых SDK, выгода может оказаться нулевой.

Установка и первый проект

В репозиториях Debian и Ubuntu пакет nim часто отстаёт от актуальной ветки. Надёжнее поставить компилятор через choosenim в домашний каталог.

curl https://nim-lang.org/choosenim/init.sh -sSf | sh
choosenim stable
nim --version
mkdir app && cd app
nimble init app
nimble build -d:release

nimble — штатный менеджер пакетов и сборщик. Команда init создаёт в текущем каталоге src/, файл app.nimble, заготовку теста и .gitignore. Запуск без установки — nim r src/app.nim.

Синтаксис: типы, процедуры, модули

Блоки выделяются отступами, типы выводятся из значений. let создаёт неизменяемую привязку, var — изменяемую, const вычисляется при компиляции. Процедуры объявляются через proc, чистые функции — через func, а звёздочка после имени экспортирует символ.

import std/[strformat, strutils]

type
  Level* = enum lvlInfo, lvlWarn, lvlError
  Event* = object
    ts*: int64
    level*: Level
    message*: string

proc parseLevel*(raw: string): Level =
  case raw.toLowerAscii
  of "info": lvlInfo
  of "warn": lvlWarn
  of "error": lvlError
  else: raise newException(ValueError, "unknown level: " & raw)

let e = Event(ts: 1, level: parseLevel("WARN"), message: "slow query")
echo &"[{e.level}] {e.ts}: {e.message}"

case по перечислению требует полноты: если добавить уровень и забыть ветку, сборка остановится. Модуль — обычный файл, а import std/... подключает стандартную библиотеку.

Память: ARC, ORC и предсказуемое освобождение

В Nim нет обязательного глобального сборщика мусора. Историческая стратегия refc сочетала подсчёт ссылок с периодическим сбором циклов, но в Nim 2.x основной режим — ORC. Он освобождает объект сразу, когда исчезает последняя ссылка, а отдельный проход разбирает циклы.

type Buffer = object
  data: ptr UncheckedArray[byte]
  len: int

proc `=destroy`(b: var Buffer) =
  if b.data != nil:
    dealloc(b.data)
    b.data = nil
    b.len = 0

var b = Buffer(data: cast[ptr UncheckedArray[byte]](alloc(1024)), len: 1024)
# память освободится при выходе из области видимости

Ручной alloc нужен редко: seq, string и Table управляются автоматически. Для внешнего C-кода добавьте =copy и =sink, иначе компилятор сгенерирует поверхностное копирование и двойное освобождение. Режим меняется флагом --mm:arc или --mm:refc, но смешивать их в проекте не стоит.

Ошибки, исключения и контракты

Nim использует исключения, но позволяет проверять их при компиляции. Прагма raises фиксирует список допустимых исключений, а defer гарантирует закрытие файла при любом выходе.

import std/[os, strutils]

proc readConfig(path: string): string {.raises: [IOError].} =
  let f = open(path)
  defer: f.close()
  result = f.readAll()

try:
  let text = readConfig("/etc/myapp.conf")
  echo text.len
except IOError as e:
  stderr.writeLine "config error: " & e.msg
  quit 1

Для библиотек удобно возвращать свой тип-результат, особенно если API вызывается из C. Не ловите CatchableError целиком: так в отчёт попадают ошибки программиста, которые лучше падать сразу. Флаг --panics:on делает нарушение границ массива неотлавливаемым.

Сборка, оптимизация и кросс-компиляция

Флаги удобно держать в nim.cfg рядом с проектом. Для релиза включайте оптимизацию, но не danger по умолчанию: этот режим убирает проверки границ и подходит только для доверенных данных.

# nim.cfg
--path:"src"
--opt:speed
--define:release
--stackTrace:on
--lineTrace:on
nim c -d:release --opt:speed src/app.nim
nim c -d:danger --passC:-flto --passL:-flto src/app.nim
nim c --os:linux --cpu:arm64 -d:release src/app.nim
ldd src/app

Кросс-компиляция требует C-тулчейна: для arm64 на x86 нужен aarch64-linux-gnu-gcc и флаг --gcc.exe:aarch64-linux-gnu-gcc. Полностью статический бинарник собирают с musl; ldd должен показать «not a dynamic executable».

Тесты, форматирование и CI

Стандартный unittest не требует внешних зависимостей, а nimble test собирает и запускает тесты из tests/. В конвейере достаточно проверки типов, тестов и форматирования.

nim check src/app.nim
nimble test
nimpretty src/app.nim

nim check быстрее полной сборки и ловит несовпадение типов. nimpretty приводит файлы к единому стилю; запускайте его в pre-commit. Версию компилятора фиксируйте в CI через choosenim.

Отладка и профилирование

Без отладочной информации стек вызовов бесполезен. Для падений собирайте бинарник с --debugger:native, а для узких мест используйте встроенный профилировщик: отчёт появится в profile_results.txt.

nim c --debugger:native --stackTrace:on --lineTrace:on src/app.nim
gdb ./src/app
nim c --profiler:on --stackTrace:on -d:release src/app.nim
./src/app
cat profile_results.txt

Для службы проверяйте коды возврата через systemctl status. Сетевые проблемы разбирайте с strace -f -p PID и ss -lntp, но только после воспроизведения в тесте.

Чек-лист перед продакшеном

  • сборка идёт с -d:release, а danger не используется без необходимости;
  • все внешние входные данные проверяются до парсинга;
  • зависимости зафиксированы, версия Nim указана в CI;
  • бинарник проверен на целевой архитектуре, а не только на машине сборки;
  • логи не содержат токенов и путей с персональными данными.

Итог

Nim даёт редкое сочетание: привычный синтаксис, нативная скорость и один исполняемый файл без зависимостей. Он подходит для утилит, сетевых служб и мест, где Python уже медленный, а C слишком опасен. Начните с небольшой утилиты, измерьте размер бинарника и потребление памяти, и только потом переносите на Nim что-то критичное.

Главное — не включать danger «на всякий случай» и не тащить ручное управление памятью туда, где хватает seq и string.

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Поделиться: Telegram ВКонтакте