Безопасная, эргономичная интероперабельность между 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. Это не соревнование языков; это два разных инструмента, которые вместе охватывают сильно разные потребности разных проектов.

Комментарии (24)


  1. OduOne
    03.10.2025 05:44

    А где сравнение, название статьи не отражает содержимое


  1. Zalechi
    03.10.2025 05:44

    Ну хоть что-то делают. Создали бы уже один головной комитет для решения концептуальных проблем программирования, и решали бы вопросики с учетом интересов всех участников рынка…

    Ой ладно…. потерплю весь этот «дерьмокод», видимо до конца своей жизни… буду ругаться на читеров, взломы банков и прочеее. Ничего, я потреплю…


  1. domix32
    03.10.2025 05:44

    Все ещё не очень понимаю кому может понадобиться Carbon. Выглядит не лучше условного D, работает даже хуже, кроссплатформы до сих пор нет. Из плюсовых проблем фактически решена только модульность и возможно часть проблем с работой с памятью, да и то под вопросом. Документации даже на стандартную либу отсутвует - непонятно что вообще в неё входит без копания в репозитории. Трейты/протоколы вижу вроде как есть, а что по тем же span/slice - совершенно непонятно. Интеграция с плюсами есть, но вопрос все тот же - а зачем, если профита от использования почти никакого.

    В этом плане cppfront куда перспективнее. Улучшена модульность, улучшена борьба со странными взаимоедйствиями компонентнов - что частично покрывает проблемы с памятью, глубокая интеграция с плюсами (главным образом потому что оно в итоге в него превращается). Кроссплатформенность едва ли не с нулевого дня. Детские болячки синтаксиса тоже решены, хотя и несколько по своему, непривычно. Если кто-то использовал GSL (Core Guidelines Support Library), то считай что всё это уже интегрировано в язык.


    1. warkid
      03.10.2025 05:44

      Карбон нужен Чандлеру - он с этого зарплату получает.


    1. joedm
      03.10.2025 05:44

      Проблема в том, что Rust имеет один фатальный недостаток.


  1. AbitLogic
    03.10.2025 05:44

    На мой взгляд бич Rust это отсутствие стандартизации, хочешь просто получить псевдослучайное число, что в паскале 1983-го даже было из коробки Random - пиши генератор или сторонний крейт, и вот пишешь под wasm, там штук 15 таких крейтов надо знать на всякую мелочь хотя бы как называются, какие-то pool_promise, gloo_timers... И каждый автор по своему трактует интерфейс


    1. lackyboye14
      03.10.2025 05:44

      Генерация случайных чисел - это платформозависимая история, std в rust должен гарантировать одинаковый результат выполнения операций на любой платформе. Плюс это будет проблемы с многопоточностью, у того же /dev/random случаются зависания при недостатке энтропии. Ну и последнее в 1987 году не так парились насчёт безопасности и криптостойкости, поэтому в тех же делфях был (вроде) рандом от времени, что потенциально несёт риски для всех криптографических алгоритмов.

      Резюмируя, в std должно попадать все, что работает прогнозируемо на любой платформе, остальное - идите в библиотеки. Поэтому, в том числе, такой цирк творится в ассинхронщине


      1. Jijiki
        03.10.2025 05:44

        а что отвечать когда спрашивают далёкие от раста, а при чем тут везде std?


        1. lackyboye14
          03.10.2025 05:44

          Исходный коммент про работу "из коробки" - значит средства языка + стандартная библиотека.

          Первую часть вопроса не понял.


      1. Kelbon
        03.10.2025 05:44

        С++ как-то справился, или вы действительно думаете, что невозможно сделать одинаковую генерацию чисел на разных процессорах?


        1. lackyboye14
          03.10.2025 05:44

          Давно у нас понятие платформа стало эквивалентно процессору?

          В плюсах <random> использует такой же алгоритм, берется источник энтропии, который дёргает системный вызов ОС. Если в ос не удалось его найти, то вызов по сути фиктивный, что влечёт проблемы с безопасностью. Вроде еще там только поддерживается не особо криптостойкий алгоритм prng, но не берусь утверждать точно, может можно другие юзать. Ещё в плюсах ты сам решаешь как бороться с неопределнным поведение в мультипотоке, когда дергаешь эти вызовы. Rust такое на уровне концепта запрещает.

          В расте не было проблем это реализовать в стандартной либе, просто идеология там другая по включению в нее, вот и все.


          1. Kelbon
            03.10.2025 05:44

            Какая разница как выбирается зерно для алгоритма? Сам рандом специфицирован и не зависит от платформы. А какое будет начальное зерно - либо вы возьмете его из времени, либо поставите фиксировано 42, либо получите из запроса в гугл-рандом - неважно

            вроде еще там только поддерживается не особо криптостойкий алгоритм prng

            не надо тащить раст мышление в плюсы. Не все алгоритмы должны быть какими-то там "криптостойкими" и мифически "безопасными". Это рандом, он считает псевдослучайные числа и распределение математически правильное. Как он может быть "некриптостойким" - понятия не имею


            1. lackyboye14
              03.10.2025 05:44

              Буквально, мой тезис написанный ДВА раза: в rust другая идеология попадания в функций в стандартную библиотеку. Перед этим, объясняю почему rand не попал в стандартную либу. В первом комментарии еще подчёркиваю проблему этого подхода на примере ассинхронщины.

              Вы:

              не надо тащить раст мышление в плюсы.

              Какой-то Милонов-style получается.

              Не все алгоритмы должны быть какими-то там "криптостойкими" и мифически "безопасными".

              Я сказал где-то, что так делать обязательно? Без проблем, считайте как хотите, rust - на уровне концепта, не даёт это сделать "из коробки", чтобы какой-нибудь программист не взял эту функцию из коробки в основу своего криптоалгоритма. Если он может так сделать, то rust потеряет свою главную фичу- безопасность. Когда программист использует сторонние либы для таких целей, то ответственность с языка снимается.

              Какая разница как выбирается зерно для алгоритма?

              Вы как представляете это: есть список алгоритмов, а ты дальше к ним пишешь свой источник энтропии? На хера эти функции тогда тащить в std, если можно вынести в отдельную либу? где ты все это получаешь СРАЗУ?

              Как он может быть "некриптостойким" - понятия не имею

              Если вы понятия не имеете, то может просто загуглите исследования по этому поводу, притом исходное название алгоритма у вас есть. Я лично не хочу заниматься просвящением и копированием ссылок на статьи по этой теме, потому что ощущение, что я "играю с голубем в шахматы"


              1. Kelbon
                03.10.2025 05:44

                Вы говорите про сишный rand, предлагаю вам зайти и убедиться уже, что речь не про него

                https://en.cppreference.com/w/cpp/header/random.html


                1. lackyboye14
                  03.10.2025 05:44

                  К чему вопрос? К криптостойкости?

                  Я конкретно говорю про mt19937, там еще дисклеймер в оригинале добавил.

                  Вроде еще там только поддерживается не особо криптостойкий алгоритм prng, но не берусь утверждать точно, может можно другие юзать

                  Если про источник энтропии, то описал в ветке ниже, как это происходит.

                  P.s. мне уже лень цитировать самого себя.


          1. Kelbon
            03.10.2025 05:44

             Ещё в плюсах ты сам решаешь как бороться с неопределнным поведение в мультипотоке, когда дергаешь эти вызовы. Rust такое на уровне концепта запрещает.

            вы вообще про сишный rand(), то есть совсем не понимаете о чём речь.

            Что мешало расту на уровне его типов запретить использовать многопоточно тип?


            1. lackyboye14
              03.10.2025 05:44

              Плюс это будет проблемы с многопоточностью, у того же /dev/random случаются зависания при недостатке энтропии

              В плюсах <random> использует такой же алгоритм, берется источник энтропии, который дёргает системный вызов ОС.

              Вы контекст не храните, когда ведёте дискуссию, я буквально описал проблему.


              1. Kelbon
                03.10.2025 05:44

                того же /dev/random случаются зависания 

                а как так вышло что мы обсуждали алгоритмы и стандартную библиотеку языка, а внезапно появился какой-то dev random? Детали реализации тут ни при чём


                1. lackyboye14
                  03.10.2025 05:44

                  внезапно появился какой-то dev random

                  если он у вас внезапно появился, то у вас проблема явно есть проблемы с хранение контекста. Ну да ладно, сделаем скидку и попробую еще раз объяснить в одном сообщении:

                  Алгоритм в c++/random и в rust/rand (не std)

                  1) в качестве источника энтропии платформозависимый источник (процессор - это не платформа, на будущее). В linux это /dev/random или getrandom(), windows - CryptGenRandom, wasm window.crypto.getRandomValues()

                  2) создаётся chacha12, в плюсах std::mt19937. Дальше генерация чисел.

                  Rust из коробки не готов брать на себя ответственность за поведение этих типов в мультипотоке. В плюсах это ответственность на программисте.


                  1. Kelbon
                    03.10.2025 05:44

                    1. mt19937 никак не зависит от платформы, его поведение специфицировано

                    2. утверждение про то что раст "не готов брать на себя ответственность за поведение этих типов в мультипотоке" это бред, потому что в расте существуют Vector строки и так далее, которые обладают такими же гарантиями в многопотоке как и генератор mt19937. И в расте есть способ выразить это в типах через трейты синк и проч


                    1. lackyboye14
                      03.10.2025 05:44

                      Я думал у меня получится в одном сообщении, активировать ваш механизм внимания и вы сможете хоть чуть чуть сохранить контекст. Но к сожалению, я был слишком наивен и лучшего мнения о собеседнике.

                      К сожалению, я не могу вместить ответ в одно предложение, чтобы вы не запутались. Поэтому предлагаю на этом остановится. Вы правы, а я не прав, как хотите, мне уже надоело.


                      1. Chaos_Optima
                        03.10.2025 05:44

                        Но вы же и правда бред несёте mt19937 не использует  /dev/random или нечто подобное, передавая одинаковый сид в него вы всегда будете получать одинаковую последовательность независимо от платформы, потому что это детерминированный алгоритм без истинной случайности. И почему вы так прицепились к криптографии хотя рандом используется в миллионе других вещей, и активнее всего как мне кажется в графике (шумы). Очень странное оправдание для раста.


  1. past
    03.10.2025 05:44

    Уже есть си с 4 плюсами. Это C#


  1. 26rus_mri
    03.10.2025 05:44

    Ничто уже не заменит С и С++. А вот некоторые полезные находки из тех, кого некоторые считают заменителями должны быть переняты.