Безопасная, эргономичная интероперабельность между Rust и C/C++ была популярной темой на RustConf 2025. Чендлер Каррут, создатель языка Carbon и руководитель команд C++, Clang, и LLVM в Google, представил доклад о различных подходах к интероперабельности в Rust и Carbon — экспериментальном языке, позиционируемого как (C++)++. Его конечный вывод заключается в том, что, хотя возможности Rust по взаимодействию с другими языками постепенно расширяются, полного решения для совместимости с C++ в ближайшее время не будет — и поэтому остаётся место для Carbon, который может предложить другой путь постепенного обновления существующих C++‑проектов. Слайды его доклада доступны тем, кто хочет подробнее изучить приведённые примеры кода.
Многие слушатели, похоже, уже знали о Carbon, и поэтому Каррут посвятил относительно мало времени объяснению мотивации создания языка. Вкратце, Carbon — это проект по созданию альтернативного фронтенда для C++, устраняющего некоторые более запутанные конструкции синтаксиса языка и позволяющего использовать лучшие аннотации для проверки безопасности памяти компилятором. Carbon задуман как полностью совместимый с C++, так что существующие проекты можно будет переписывать на Carbon пофайлово, желательно без изменения компилятора или системы сборки. Carbon пока непригоден для практического использования — его разработчики работают над проработкой более сложных деталей языка, и из доклада Каррута было понятно, почему.
«Всегда немного волнительно говорить о языке программирования, который не Rust, на RustConf», — начал Каррут, вызвав общий смех. Он много лет работал с C++ и участвует в Carbon с самого начала проекта в 2020 году. Сейчас его работа над Carbon оплачивается в рамках команды Google по языкам и компиляторам. Он кратко показал исследование Google, из которого следовало, что большинство уязвимостей безопасности могли бы быть предотвращены при использовании языков с безопасностью памяти, но долго на этом не останавливался, ожидая, что аудитория RustConf и так прекрасно осведомлена о плюсах memory safety.
Проблема в том, что огромное количество существующего ПО написано на C и C++. И никакой волшебной палочки, чтобы это ПО исчезло, не существует. Миграция этого кода на языки с безопасностью памяти потребует интеграции новых языков в существующую экосистему, сказал он. Интероперабельность — это не просто «приятный бонус», а ключевой фактор, делающий возможным переход на memory‑safe языки.
У Rust уже есть несколько инструментов для взаимодействия с кодом на C/C++. Каррут перечислил нативный интерфейс внешних функций Rust, bindgen и cbindgen, crate cxx и собственный проект Google — Crubit. Но, по его словам, ни один из этих инструментов не является действительно хорошим решением для существующего ПО на C++. Он предложил рассматривать софт на спектре от «greenfield» (новый код, не жёстко связанный с C++, с чёткими границами абстракций) до «brownfield» (тесно переплетённый с существующим C++ и имеющий огромную API‑поверхность). Greenfield‑код относительно легко портировать на Rust — модуль за модулем, используя существующие средства связывания. Brownfield проекты же переносить гораздо сложнее, так как его нельзя просто разделить, и интерфейс между C++ и Rust становится куда более сложным и двунаправленным.
Вопрос, по словам Каррута, в том, сможет ли Rust когда‑либо закрыть этот разрыв? Он так не считает — по крайней мере, не скоро и не без колоссальных усилий. Но Rust — не единственный путь к memory safety. В идеале существующий C++‑код можно было бы сделать безопасным прямо на месте. Множество людей пытались это сделать, но, как он выразился: «Комитет C++ вряд ли на это пойдёт». Добавить безопасность памяти в C++ в его текущем виде просто невозможно.
Тем не менее есть примеры языков, которым удалось эволюционировать от базового языка к более гибкому и безопасному: TypeScript стал развитием JavaScript, Swift — развитием Objective‑C, а сам C++ — развитием C. Каррут считает, что Carbon может стать похожей эволюцией C++ — путём постепенной миграции к безопасному языку с упором на наиболее «застрявший» brownfield‑код. Rust подходит к проблеме memory safety со стороны greenfield, а Carbon — с другой стороны. Именно это делает Rust и Carbon столь разными языками.
Более пристальный взгляд
Главное внимание в своём докладе Каррут уделил тому, чтобы показать, в чём именно заключаются различия, и где, по его мнению, языки могут учиться друг у друга. Синтаксис Rust и Carbon «не слишком различается»; его больше интересовали абстрактные отличия.
Например, в Rust единицей компиляции является целый crate, который может состоять из нескольких модулей. Поэтому допускаются циклические ссылки между модулями, и это работает. В Carbon такого быть не может, потому что «существующий C++‑код зачастую странным образом зависит» от возможности компилировать отдельные файлы. Поэтому Carbon наследует модель C++, со всеми её прямыми объявлениями (forward declarations), (опциональными) заголовочными файлами и большей сложностью на этапе линковки. Это делает модель Carbon более сложной, но эта сложность не берётся из ниоткуда — «она приходит из C++».
Другой пример — различие между трейтами и классами. Rust‑трейты и Carbon‑классы синтаксически не так уж сильно отличаются — в Carbon методы пишутся прямо внутри определения структуры, а в Rust — отдельно. Но концептуально это совершенно разные подходы. Carbon приходится поддерживать наследование, виртуальные функции, защищённые поля и так далее. «Это та самая сложность, которой у Rust просто нет и которой ему не приходится заниматься». Carbon стремится работать с C++‑API в их исходном виде. Более того, можно даже наследоваться через границу C++/Carbon.
Такие различия, отметил он, пронизывают все части языка. Перегрузка операторов, дженерики, преобразования типов — всё это оказывается сложнее в Carbon. Почему так? Зачем вообще добавлять всю эту сложность? Чтобы объяснить, он привёл пример гипотетического, но вполне обычного C++-API:
int EVP_AEAD_CTX_seal_scatter(
const EVP_AEAD_CTX *ctx,
std::span<uint8_t> out,
std::span<uint8_t> out_tag,
size_t *out_tag_len,
std::span<const uint8_t> nonce,
std::span<const uint8_t> in,
std::span<const uint8_t> extra_in,
std::span<const uint8_t> ad);
Этот пример был адаптирован из реальной функции в криптографической библиотеке BoringSSL. Каждый std::span
— это комбинация указателя и длины. Основная проблема при точном представлении этого API в Rust даже не видна в самом коде; документация к функции объясняет, что out
должен быть либо тем же указателем, что и in
, либо полностью не пересекаться с ним в памяти. Если указатели совпадают, функция шифрует входной буфер «на месте». В противном случае зашифрованный результат записывается в выходной буфер, не затрагивая входной. Остальные указатели не должны алиаситься.
В Carbon планируют решать такие задачи с помощью так называемых alias sets («наборов алиасов»). Это будут аннотации, которые показывают, какие указатели могут пересекаться, а какие — нет. Получающийся код на Carbon мог бы выглядеть так:
fn EVP_AEAD_CTX_seal_scatter[^inout](
ctx: const EVP_AEAD_CTX ^*,
out: slice(u8 ^inout),
out_tag: slice(u8 ^),
out_tag_len: u64 ^*,
nonce: slice(const u8 ^),
input: slice(const u8 ^inout),
extra_input: slice(const u8 ^),
ad: slice(const u8 ^)) -> i32;
Здесь inout
— это имя для конкретного alias set, которым помечены out
и input
. Все остальные указатели в сигнатуре функции не имеют такого набора, и компилятор гарантирует, что они не будут пересекаться.
Попробовать выразить этот API на Rust — не получится. Язык просто не допускает, чтобы изменяемые ссылки алиасились, и приходится создавать два разных wrapper‑функции с разными сигнатурами — для случая «in‑place» и для случая «copying». Переписывание модуля, содержащего такую функцию, в Rust превращается в сложный процесс, сочетающий простую трансляцию кода и серьёзный рефакторинг интерфейсов.
Сила Carbon в плане интероперабельности, по словам Каррута, заключается в том, что он позволяет разделить эти шаги и выполнять их по отдельности и постепенно. Он показал ещё один пример C++‑программы, которая фактически была безопасной с точки зрения памяти, но несовместимой с анализом времени жизни в Rust. Ни один инструмент автоматического анализа не может быть абсолютно совершенным, поэтому Carbon вряд ли будет сильно лучше в этом смысле, — но в Carbon шаблоны, которые компилятор не может доказать как безопасные, можно превращать не в ошибку, а в предупреждение.
Этот акцент на «работе с C++ как он есть» делает Carbon другим языком. Он оказывается специально заточенным под интероперабельность и постепенную миграцию — что не бесплатно. Это делает язык сложнее, чем он мог бы быть. Каррут не считает, что это правильный компромисс для любого языка. Но если цель — достичь безопасности памяти во всём программном мире, то, по его мнению, должно быть место и для Rust, и для Carbon. Это не соревнование языков; это два разных инструмента, которые вместе охватывают сильно разные потребности разных проектов.