
Цифровой дайджест #1
Каждое воскресенье я готовлю три выпуска дайджестов для компании Start X:
- для сотрудников ИТ и ИБ;
- для разработчиков;
- для обычных пользователей.
В процессе работы над дайджестом я читаю сотни новостей в RSS-читалке. И некоторые из них привлекают моё внимание больше, чем другие. В силу их значимости или интереса лично для меня. Сегодня я понял, что не могу больше откладывать такие новости на потом. Буду делать обзор того, что заинтересовало, чтобы потом самому перечитать было интересно.
Герой сегодняшнего выпуска — типобезопасный компилятор для языка C под названием Fil-C.
Fil-C: безопасность памяти для C без переписывания кода
Пока индустрия спорит о переходе на Rust и Zig, появился неожиданный игрок — Fil-C. Это не новый язык, а радикально переосмысленный компилятор для обычного C/C++, который ловит все опасные операции с памятью прямо во время выполнения программы.
Что это и зачем
Fil-C — это форк современного Clang (версия 20.1.8), который добавляет полную динамическую проверку памяти ко всем небезопасным операциям. Главная фишка: вам не нужно переписывать код. Берёте существующий проект на C — CPython, OpenSSH, GNU Emacs, Wayland — собираете с помощью Fil-C, и получаете защиту от переполнений буфера, use-after-free и компании.
Звучит как магия? Почти так и есть, но с интересными инженерными решениями под капотом.
Технология InvisiCaps: невидимые суперспособности для указателей
Секретный соус называется InvisiCaps (Invisible Capabilities). Каждый указатель в программе незаметно для неё самой несёт метаданные: границы выделенной памяти, права доступа, привязку к конкретному объекту.
Попытались прочитать за пределами массива? Паника. Обратились к освобождённой памяти? Паника. Записали в read-only область? Паника.
При этом указатели остаются стандартного размера (64 бита на 64-битных системах), а большинство идиом C, включая некоторые формально undefined behavior из реальной практики, продолжают работать.
Дополняет картину FUGC — параллельный сборщик мусора, который работает без остановки всех потоков и даёт детерминированный отлов двойного освобождения памяти. Объекты не перемещаются, накладные расходы — простой store-барьер.
Что говорит легенда
Дэниел Дж. Бернстайн (D. J. Bernstein, он же djb) — одна из знаковых фигур в мире системного программирования и криптографии. Автор qmail (почтовый сервер, который годами работал без единой уязвимости), djbdns, curve25519 и crypto-библиотеки NaCl. Известен фанатичным вниманием к безопасности и корректности кода. Когда такой человек хвалит инструмент — это о чём-то говорит.
Бернстайн провёл масштабное тестирование Fil-C:
По совместимости: «Многие библиотеки и приложения, которые я пробовал, работают без изменений». Он успешно собрал bash, coreutils, CPython, OpenSSH, OpenSSL, vim, zsh, zstd и десятки других пакетов. Его цель — перевести на Fil-C все машины, которыми он управляет.
По производительности: Бернстайн прогнал около 9000 микробенчмарков на своём криптографическом коде (самая требовательная нагрузка). Результат: замедление от 1× до 4× по сравнению с обычным Clang на AMD Zen 4. Для IO-bound приложений overhead часто вообще незаметен.
Практические детали: Есть нюансы — нет vfork (пришлось патчить Boost build), не работает с Valgrind, некоторые системные вызовы требуют внимания. Но это решаемые проблемы, не архитектурные ограничения.
Для энтузиастов
Бернстайн выпустил Filian — набор инструментов для Debian 13, который автоматически пересобирает выбранные пакеты под Fil-C (в отдельную архитектуру amd64fil0). Есть и Filnix от Mikael Brockman — пакет для Nix. Можно быстро пощупать на практике.
Код Fil-C под Apache 2.0, runtime под BSD-2-Clause. Поддерживаются musl и glibc. Доступны дистрибутивы для сборки полностью безопасного Linux-userland.
Почему это важно
Fil-C — это не компромисс «или совместимость, или безопасность». Это попытка получить обе вещи сразу:
- не нужно переписывать миллионы строк существующего кода на Rust/Zig;
- защита от целых классов уязвимостей становится автоматической;
- привычная экосистема — make, CMake, Meson, обычный workflow;
- предсказуемая цена — замедление в 1-4 раза для кода с «тяжёлыми» вычислениями, для обычных приложений, как правило, меньше.
Конечно, 4× замедление — это не бесплатно. Но для критичного к безопасности кода (а сегодня это почти всё, что видит сеть) это может быть разумный трейд-офф. Особенно если альтернатива — полное переписывание на новый язык.
Fil-C доказывает: можно защитить C, не убивая C. Вопрос теперь в том, готова ли индустрия принять такой подход.
Чтобы два раза не вставать, выскажусь и по поводу модных «безопасных» языков
Rust и Zig: что теряем при переходе с C
Rust и Zig позиционируются как современная замена C, но у медали есть обратная сторона. Вот о чём редко говорят евангелисты этих языков.
Проблема №1: Экосистема — это не только язык
50+ лет наследия C — это не просто синтаксис. Это:
- миллиарды строк рабочего кода в production;
- тысячи библиотек с устоявшимся API;
- инструменты отладки, профилирования, анализа, отточенные десятилетиями;
- знания в головах разработчиков;
- интеграция с операционными системами на уровне «родного языка».
Rust требует переписать всё с нуля. FFI (Foreign Function Interface) для вызова C-кода работает, но это костыль: вы теряете все гарантии безопасности Rust на границе, плюс получаете overhead на маршалинг данных. Хотите использовать старую надёжную библиотеку? Либо пишите unsafe-обёртку, либо ищите переписанную на Rust (часто недозрелую) версию.
Zig лучше в плане совместимости — он может напрямую импортировать C-заголовки и компилировать C-код. Но это односторонняя интеграция: C-проект не может просто так взять кусок Zig-кода. Вы всё равно стоите перед выбором: постепенная миграция с гибридной кодовой базой или большой переписывание.
Проблема №2: Сложность vs простота
C прост до неприличия. Вся спецификация языка умещается в голове одного разработчика. Указатель — это адрес, структура — это байты в памяти, функция — это точка входа. Что видишь, то и получаешь.
Rust — это когнитивная перегрузка:
- система владения данными с временами жизни переменных — нужно постоянно думать, кто и как долго может использовать каждый кусок памяти;
- правила заимствования, которым посвящены многостраничные объяснения — когда можно передать ссылку, когда нельзя, почему компилятор ругается;
- система трейтов с ассоциированными типами, где простой на первый взгляд код превращается в головоломку с ограничениями обобщённых типов;
- два вида макросов — процедурные и декларативные, каждый со своими правилами;
- асинхронное программирование с закреплением в памяти, футурами и исполнителями — концепции, которые нужно понять, прежде чем написать простой сетевой код;
- сотни страниц документации, которые действительно нужно прочитать, чтобы быть продуктивным.
Время обучения junior-разработчика C до продуктивности — недели. Rust — многие месяцы. Спросите любого, кто учил Rust: первые пару месяцев — это борьба с borrow checker, а не решение задач.
Zig проще Rust, но добавляет свои сложности:
- Comptime — код, выполняемый во время компиляции, с неочевидными ограничениями
- Явное управление аллокаторами везде (принципиально, но многословно)
- Ошибки как значения через union types — каждый вызов функции обвешан обработкой
- Язык ещё не стабилизирован — breaking changes между версиями
Проблема №3: Время компиляции
C компилируется быстро. Инкрементальная сборка большого проекта — секунды. Полная пересборка ядра Linux — минуты.
Rust компилируется мучительно долго. Сотни зависимостей через Cargo, каждая со своими generic-монстрами. Первая сборка проекта средней сложности — десятки минут. Изменили одну строку — минута на пересборку. Это убивает продуктивность: вы не можете быстро итерироваться, не можете держать контекст в голове между сборками.
Типичная история: переписали C-проект на Rust, тесты стали прогоняться не за 30 секунд, а за 10 минут. CI превратилась в узкое место.
Zig лучше, но всё равно медленнее C из-за comptime-вычислений.
Проблема №4: Размер бинарников и потребление ресурсов
C даёт компактные бинарники. Hello World — несколько килобайт. Даже сложная программа — мегабайты.
Rust тащит за собой:
- массивную стандартную библиотеку;
- среду выполнения для асинхронного кода (если используется);
- механизм безопасной обработки паник с автоматической очисткой ресурсов — это требует дополнительных таблиц и кода в программе;
- монорфизацию обобщённых типов — каждый вариант использования создаёт отдельную копию кода.
Hello World на Rust — сотни килобайт. Реальная программа — десятки мегабайт. Для встраиваемых систем или минималистичных окружений это проблема.
Zig контролируемее, но всё равно крупнее компактного C-кода.
Проблема №5: Стабильность и зрелость
C — это стандарт. C89, C99, C11, C17 — эволюция плавная, обратная совместимость железобетонная. Код, написанный 30 лет назад, компилируется сегодня.
Rust до версии 1.0 дошёл только в 2015. Каждые 6 недель новая версия. Да, обещают совместимость, но:
- экосистема библиотек постоянно ломается — сторонние библиотеки (в Rust они называются “крейты”) обновляются несинхронно, зависимости конфликтуют между собой, что работало вместе полгода назад, сегодня может не собраться;
- идиомы меняются — “правильные” способы писать код эволюционируют, старый код выглядит устаревшим и его приходится переписывать (например, асинхронное программирование до 2019 года работало совсем иначе);
- инструментарий эволюционирует — система сборки, форматировщики, линтеры меняют поведение, то что работало год назад, сегодня помечено как устаревшее или требует других флагов компиляции.
Zig вообще пре-1.0. Язык официально нестабилен. Код, написанный на Zig 0.11, не компилируется на 0.12. Для production это рулетка.
Проблема №6: Предсказуемость vs магия
C предсказуем. Вы пишете код и примерно представляете, во что он превратится в ассемблере. Нет скрытых аллокаций, нет неожиданных копирований, нет магии.
Rust полон неявного поведения:
- автоматический
Derefможет вызывать цепочки методов, которые вы не видите; Drop(деструкторы) выполняются автоматически — иногда неожиданно рано или слишком поздно;- макросы генерируют код, который вы не контролируете;
- move semantics — копирование или перемещение? Зависит от
Copytrait, который может быть неочевиден.
Отладка Rust-кода иногда превращается в настоящее расследование: что же там на самом деле происходит?
Zig лучше с предсказуемостью, но comptime может выстрелить в ногу неожиданными способами.
Проблема №7: Привязка к единственному компилятору
C — открытый стандарт. Есть GCC, Clang, MSVC, ICC, десятки других компиляторов. Не нравится один — переключайтесь на другой.
Rust — это фактически монокультура rustc. Альтернативные компиляторы (mrustc, gcc-rs) существуют на бумаге, но далеки от feature parity. Вы привязаны к LLVM и решениям команды Rust.
Zig — вообще один компилятор от одного человека (Andrew Kelley и небольшой команды). Bus factor пугающий.
Когда Rust/Zig имеют смысл
Справедливости ради. Преодоление описанных сложностей имеет смысл, если:
- вы делаете новый проект с нуля, где безопасность критична — Rust даёт гарантии на уровне компилятора;
- команда готова вкладываться в обучение и принимает компромиссы по времени разработки и сложности;
- нет привязки к существующему C-коду и можно выбирать экосистему библиотек с чистого листа;
- вы работаете с WebAssembly, криптографией, парсерами — в области, где Rust показывает себя особенно хорошо.
Но если у вас:
- большая существующая кодовая база на C/C++;
- требования к быстрой разработке с частыми изменениями;
- ограниченный бюджет на поиск и найм разработчиков;
- встраиваемые системы с жёсткими ограничениями по памяти и размеру программ;
- нужна простота поддержки кода на годы вперёд,
…то переход на Rust/Zig может оказаться дороже, чем вы думаете.
Почему Fil-C — это третий путь
Fil-C предлагает радикально иной подход: не меняйте язык, измените компилятор. Вы получаете безопасность памяти, не жертвуя:
- существующим кодом;
- скоростью компиляции;
- простотой языка;
- доступностью разработчиков;
- зрелостью экосистемы.
Да, есть замедление программ во время работы из-за проверок безопасности. Но оно предсказуемо и измеримо (1-4× по тестам Бернстайна), в отличие от рисков полного переписывания проекта на новый язык.
Rust и Zig — отличные языки для своих ниш. Но они не серебряная пуля, и переход на них — не бесплатный. Иногда эволюция лучше революции.