Odin — компилируемый язык для системного и игрового программирования, который сознательно отказывается от привычной объектной надстройки. В нём нет классов, наследования, исключений и скрытых аллокаций; есть процедуры, структуры, параметрический полиморфизм и явные аллокаторы. Цель — предсказуемый код, который быстро компилируется и так же быстро работает.
Место в ряду системных языков
Odin появился в 2016 году как эксперимент Билла Гингера и вырос в практический инструмент для game dev, графики и утилит. Его ниша — между C и современными языками: синтаксис чище C, но без сборщика мусора и без «магии» в рантайме. Компилятор использует LLVM, поэтому качество оптимизации знакомое, а время сборки остаётся коротким даже на средних проектах.
Язык не пытается быть универсальным. Здесь нет интерфейсов и классов, зато есть using, union-типы и процедурные указатели. Нет перегрузки операторов и неявных числовых преобразований — код читается однозначно, но требует дисциплины.
Установка
Проще всего взять готовый архив с GitHub Releases: внутри компилятор и стандартная библиотека core. Распакуйте его в /opt и добавьте в PATH.
unzip odin-linux-amd64-*.zip -d /opt/odin
export PATH="/opt/odin:$PATH"
odin version
odin check .
Если нужна сборка из исходников, клонируйте репозиторий и запустите build_odin.sh; понадобятся clang и LLVM. Для командной работы фиксируйте версию компилятора: язык стабилен, но ежемесячные dev-сборки иногда меняют детали core.
Первая программа
Точка входа — процедура main в пакете main. Импорты указываются строкой, точка с запятой не нужна, фигурные скобки обязательны.
package main
import "core:fmt"
main :: proc() {
fmt.println("Привет, Odin")
}
Сборка и запуск выглядят так:
odin run .
odin build . -o:speed -out:app
odin check . -vet -strict-style
Флаг -o:speed включает оптимизацию, -out задаёт имя бинарника. По умолчанию сборка идёт с отладочной информацией; для минимального размера используйте -o:size. Заголовочных файлов нет: компилятор видит весь пакет целиком и не требует объявлять прототипы.
Синтаксис, который надо принять
Объявление переменной выглядит как name: Type = value, вывод типа — через :=. Массивы фиксированной длины — [4]int, срезы — []int, динамические массивы — [dynamic]int, ассоциативные — map[string]int.
Цикл только один — for, но у него несколько форм: классическая с инициализацией, по диапазону, по срезу и бесконечная. Условия пишутся без скобок. Процедуры возвращают несколько значений, а ошибку принято возвращать вторым результатом.
import "core:strconv"
parse :: proc(s: string) -> (i64, bool) {
return strconv.parse_int(s)
}
main :: proc() {
n, ok := parse("42")
if !ok { return }
defer fmt.println("готово")
fmt.println(n)
}
defer выполняет вызов при выходе из области видимости, а or_return сокращает проверку: value := f() or_return. Это не исключения — поток управления остаётся явным и виден в тексте функции.
Память и аллокаторы
Память по умолчанию выделяется через context.allocator. Его можно подменить на арену, пул или собственный аллокатор — тогда все new, make и append внутри области используют новую стратегию. Это удобно для кадровых вычислений и парсеров, где нужен массовый сброс.
arena: mem.Arena
backing: [1 << 20]byte
mem.arena_init(&arena, backing[:])
context.allocator = mem.arena_allocator(&arena)
ptr := new(int)
ptr^ = 42
defer mem.arena_free_all(&arena)
Для временных данных есть context.temp_allocator; в конце кадра его очищают через free_all. Динамические массивы и map освобождаются delete, а не сборщиком мусора. Забытая очистка не приведёт к падению, но приведёт к росту памяти — здесь язык полагается на разработчика.
Полиморфизм и метапрограммирование
Параметрический полиморфизм строится на $T и where. Ограничения проверяются на этапе компиляции, поэтому лишних проверок в рантайме нет.
sum :: proc(xs: []$T) -> T where intrinsics.type_is_numeric(T) {
total: T
for x in xs { total += x }
return total
}
when выполняет ветвление на этапе компиляции, а #config, #load и #caller_location дают управление без препроцессора. Отдельного языка макросов здесь нет: вместо него — параметризация и compile-time конструкции.
Связь с C — через foreign import. Заголовки не нужны, достаточно объявить сигнатуры; библиотека для линковки указывается атрибутами. Это позволяет постепенно переписывать существующий C-код, не перекладывая проект целиком.
Практика и инструменты
Стандартная библиотека разбита на core и vendor; в core есть fmt, os, strings, mem, math, time, sync и контейнеры. Тесты пишутся в том же пакете и запускаются через odin test.
odin test .
odin doc .
odin build . -target:linux_amd64 -out:app-linux
odin build . -target:windows_amd64 -out:app.exe
Кросс-компиляция работает без отдельного тулчейна, если не тянуть системные C-библиотеки. Для Linux-сервиса это удобно: собрали на рабочей машине, положили один бинарник на сервер и не устанавливали рантайм. Это особенно полезно в CI, где нет прав на установку пакетов.
Минимальный CI-сценарий выглядит скучно и надёжно: проверка с -vet, тесты, затем сборка под нужную платформу. Артефакт — один файл, поэтому деплой сводится к копированию и перезапуску службы. Для контейнера подойдёт scratch-образ: зависимостей времени выполнения нет.
- проверка типов и стиля на каждом коммите;
- тесты рядом с кодом;
- фиксированная версия компилятора;
- сборка под целевую платформу в CI.
Ограничения
Экосистема небольшая: часть библиотек придётся писать самому или оборачивать C. Интерфейсов нет, поэтому абстракции строят на union и процедурных полях структур — это гибко, но требует аккуратности и опыта.
Язык развивается итеративно, и dev-сборки могут ломать совместимость. Для продакшена фиксируйте компилятор, храните его версию в CI и прогоняйте odin check с -vet. Документация частично живёт в исходниках core, поэтому привычка читать объявления обязательна. Отладчик gdb или lldb работает, но после оптимизации имена меняются — сложные падения разбирают на сборке с -debug.
Итог
Odin — практичный выбор, когда нужен C-подобный контроль без препроцессора и скрытых затрат: инструменты, игры, обработка данных, небольшие сервисы. Начните с одного модуля, проверьте эргономику на реальной задаче и только потом переносите остальное.
Ключевые привычки: явные аллокаторы, defer для очистки, проверка в CI и зафиксированная версия компилятора. Если команда готова отказаться от классов и исключений, язык отдаёт предсказуемостью и скоростью сборки.