Тема безопасного и эргономичного взаимодействия между Rust и C/C++ была популярна на конференции RustConf 2025, состоявшейся в Сиэтле, штат Вашингтон. Чендлер Каррут выступил с презентацией, в которой представил различные способы взаимодействия между Rust и Carbon — экспериментальным языком, который можно условно обозначить как «(C++)++». Он резюмировал, что, пусть возможности стыковки Rust с другими языками со временем расширяются, в обозримом будущем не стоит ожидать, что будет полноценно решена проблема его взаимодействия с C++. Поэтому как раз появляется ниша для Carbon, который может предложить иной подход для постепенного совершенствования существующих проектов на C++. Вот слайды к его презентации — для тех, кто хотел бы изучить код его примеров более подробно.
Сложилось впечатление, что многие из собравшихся уже знают о Carbon, так что Каррут относительно кратко пояснил, зачем нужен этот язык. Не вдаваясь в детали, проект Carbon призван создать альтернативный фронтенд для C++ — такой который позволял бы частично избавиться от малопонятных аспектов синтаксиса C++, обеспечить более качественное аннотирование для компилятора, который мог бы проверять безопасность памяти. Язык Carbon задуман как полностью совместимый с C++, так, чтобы уже существующие проекты на C++ можно было переписать на Carbon файл за файлом, в идеале не внося никаких изменений ни в компилятор, ни в систему сборки. Пока Carbon не готов для реального использования — участники проекта занимаются уточнением наиболее сложных деталей языка.
Каррут много лет занимается языком C++, а разработкой Carbon занялся в 2020 году — то есть, с первых дней существования этого проекта. В настоящее время он занимается Carbon в составе команды по разработке языков и компиляторов в Google, и эта работа оплачивается. На презентации Каррут кратко представил некоторые исследовательские разработки Google, показал большинство уязвимостей безопасности, которыми сейчас занимается, такими, которые можно было бы устранить в языках, претендующих на безопасность памяти.
Реальность такова, что в мире очень много программ, написанных на C и C++.
Эти программы никуда не денутся по мановению волшебной палочки. При переносе любой из этих программ на языки, обеспечивающие безопасность памяти, потребуется интегрировать эти языки со всей остальной программной экосистемой. Поэтому интероперабельность — не просто приятная возможность, которую неплохо иметь, а ключевой аспект работоспособности языков, обеспечивающих безопасность памяти.

В Rust уже есть ряд инструментов, благодаря которым проще организовать его взаимодействие с кодом на C/C++. В частности, Каррут упомянул нативный интерфейс Rust для работы со сторонними функциями, bindgen и cbindgen, пакет cxx, а также проект Crubit, разрабатываемый в Google. Но, на его взгляд, ничто из этого как следует не работает с современным софтом, написанным на C++. По его классификации, весь софт существует в диапазоне от «свежих» проектов (greenfield) – это новый код, не имеющий тесной привязки к C++, причём, с хорошими границами абстракций — до «перестраиваемых» (brownfield) — таких, которые тесно связаны с существующим кодом на C++ и имеют обширную поверхность API. Свежий софт относительно легко портировать на Rust — это можно сделать модуль за модулем, пользуясь уже имеющимися инструментами связывания. Работать с перестраиваемым софтом в разы сложнее, поскольку он плохо поддаётся декомпозиции. Поэтому интерфейс между кодом на C++ и кодом на Rust обязательно получится гораздо более сложной зоной с двусторонним движением.
Вопрос в том, сможет ли Rust когда-либо закрыть этот пробел? Каррут так не думает — или, как минимум, это произойдёт нескоро и потребует монументальных усилий. Но безопасность памяти можно обеспечить не только при помощи Rust. В идеале её нужно реализовать прямо в существующем коде C++. Многие пытались это сделать, но «Комитет C++ за это, пожалуй, не возьмётся». По-видимому, не существует способа накатить на C++ безопасность памяти на C++, пока язык остаётся в том виде, каков он сейчас.
Есть несколько языков, которым удалось уйти от языка-прародителя к более гибкому и удобному языку-наследнику. В результате эволюции JavaScript появился TypeScript, язык Swift — потомок Objective-C, а сам C++ возник в результате эволюции C. Каррут считает, что Carbon мог бы аналогичным образом развиться из C++. Сейчас Carbon находится на пути постепенного превращения в язык с повышенной безопасностью, причём, нацелен на переработку самого запущенного софта из категории «brownfield». Rust пытается решать проблему безопасности, прежде всего, для свежего софта, то есть, Rust и Carbon подходят к этой проблеме с разных сторон. Именно этим они и отличаются.
❯ Подробнее о языке Carbon
Самое интересное — подчеркнуть эти отличия и рассмотреть, в чём каждый из двух языков наиболее своеобразен. Синтаксисы у Rust и Carbon «отличаются не слишком сильно», и Каррут сосредоточился на более абстрактной разнице. Например, в Rust единицей компиляции является целый пакет (крейт), который потенциально может состоять из нескольких модулей. Следовательно, модули могут ссылаться друг на друга, в том числе, циклически — и это просто работает. Как раз такой принцип Carbon поддерживать не может, поскольку «имеющийся код C++ часто обладает странной зависимостью» от того, чтобы было можно поодиночке компилировать отдельные файлы. Таким образом, Carbon наследует модель C++ со всеми его предварительными объявлениями, (опциональными) отдельными заголовочными файлами и повышенной сложностью линковщика. Соответственно, модель Carbon сложнее, чем модель C++, но эта сложность взялась не из ниоткуда — «она следует из C++».
Также важно отметить разницу между типажами и классами. Типажи Rust и классы Carbon с синтаксической точки зрения не так сильно отличаются. Carbon просто записывает методы внутри определения структуры, а Rust записывает их отдельно. Но между ними есть серьёзные концептуальные отличия. Carbon приходится обрабатывать наследование, виртуальные функции, защищённые поля, т.д. Всё это и есть та сложность, которая в Rust отсутствует и поэтому там в обработке не нуждается. Carbon пытается соответствовать API C++ там, где они есть. Даже возможно наследование, проникающее через границу C++/Carbon.
Отличия такого рода повсюду, и они — неотъемлемая часть языка. Перегрузка операторов, дженерики и преобразование типов в Carbon систематически сложнее, чем в C++. Почему так? Оправданна ли эта дополнительная сложность? Чтобы ответить на этот вопрос, давайте рассмотрим гипотетический, но вполне типичный API C++:
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 записана комбинация указателя и длины. Основная проблема, возникающая при необходимости безошибочно представить это в Rust, не видна в коде как таковом. Но в документации к этой функции сказано, что на месте out должен быть либо тот же самый указатель, что и на месте in, либо они должны занимать совершенно не пересекающиеся области памяти. Когда два указателя идентичны, функция прямо на месте шифрует заданный ввод, поступивший в виде обычного текста. В противном случае зашифрованный вывод записывается в выходной буфер, и входной буфер мы при этом не тревожим. Не предполагается совмещение ни для одного из этих указателей.
Работа над Carbon продолжается прямо сейчас, и в настоящее время планируется выражать API подобного рода таким образом, чтобы их могла проверять сама машина, и для этого использовать «наборы псевдонимов». Это будут аннотации, прописывающие, какие указатели допустимо совмещать друг с другом, а какие — нет. В результате на 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 — имя, присваиваемое конкретному набору псевдонимов и используемое для аннотирования out и input. Для всех остальных указателей в сигнатуре этой функции наборы псевдонимов не указаны, поэтому компилятор обеспечит, что они не смогут совмещаться.
Если вы попытаетесь представить этот API на Rust, у вас просто ничего не получится. Этот язык просто не позволяет изменяемым ссылкам совмещаться друг с другом, так что у вас получатся две отдельные функции-обёртки с разными сигнатурами, причём, одна из них будет применяться для замены на месте, а другая — на случай копирования. Если потребуется переписать на Rust модуль, который содержал бы такую функцию, то это будет очень непросто, и вся работа будет состоять из двух перемежающихся процессов: простой перевод кода с языка на язык и рефакторинг интерфейса.
Сильная сторона Carbon заключается в интероперабельности. На нём эти процессы можно отделить друг от друга и выполнять каждый маленький этап отдельно. В другом примере на C++ Каррут показал программу, которая на самом деле обеспечивает безопасность памяти, но несовместима с применяемым в Rust анализом времён жизни. Никакой компьютеризированный анализ безопасности памяти не может быть идеален, так что Carbon, предположительно, не сильно поправит эту ситуацию. Но в Carbon такие паттерны, безопасность которых для памяти компилятор не может доказать, превращаются в предупреждения, а не в ошибки.
Важно сосредоточиться на том, чем Carbon отличается от C++. Carbon целенаправленно подогнан для взаимодействия с другими языками и постепенной миграции, а за это приходится платить. Поэтому язык получается сложнее, чем он мог бы быть, и едва ли такой компромисс подходит для любого языка. Но, когда ставится цель обеспечить безопасность памяти в пределах целой софтверной экосистемы, там найдётся место и для Rust, и для Carbon. Это не два совершенно разных языка, а два языка, работающих в паре, когда нужно сочетать сильно различающиеся потребности разных проектов.
Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале ↩
Комментарии (3)

SilverTrouse
28.10.2025 09:49Все еще можно писать на С++. Современный С++ сильно хорош ( да есть тяжело читаемое легаси но это легаси)
domix32
Всё ещё не понимаю почему они пытаются продать людям Carbon.
Рабочего компилятора неткомпилятор есть, но кроссплатформой и не пахнет, перегрузки, шаблоны и система типажей сложнее чем C++. inout/restrict хоть и отсутствуют что в Rust, что в С++, но и там и там есть свои способы это обойти и проверить на этапе компиляции. Система интерфейсов выглядит как пересечение плюсовых концептов и ржавых типажей, то есть не сказать чтобы что-то новое, разве что синтаксис (может быть) попроще. Вместо решения проблем с плюсовыми заголовочниками и модулями там предлагают ещё пару альтернативных вариантов интеграции различных модулей в сборку - уже представляю сколько carbon make кода надо для подобного писать. Кому этот язык может понадобиться?