Библиотеки Rust
Интеграция библиотеки Rust будет описана на примере интеграции хеш-функции BLAKE3.
Первым шагом интеграции является добавление библиотеки в каталог /rust. Для этого нужно создать пустой Rust-проект и подключить требуемую библиотеку в Cargo.toml. Также необходимо настроить компиляцию новой библиотеки как статической, добавив crate-type = ["staticlib"] в Cargo.toml.
Далее нужно связать библиотеку с CMake с использованием библиотеки Corrosion. Сначала нужно добавить каталог библиотеки в CMakeLists.txt внутри каталога /rust. После этого следует добавить файл CMakeLists.txt в каталог библиотеки. В нём необходимо вызвать функцию импорта Corrosion. Для импорта BLAKE3 использовались следующие строки:
Таким образом, мы создадим корректную цель CMake с помощью Corrosion, а затем переименуем её в более удобное имя. Обратите внимание, что имя _ch_rust_blake3 берётся из Cargo.toml, где оно используется как имя проекта (name = "_ch_rust_blake3").
Поскольку типы данных Rust несовместимы с типами данных C/C++, мы будем использовать наш пустой библиотечный проект для создания адаптеров (shim-методов) для преобразования данных, полученных из C/C++, вызова методов библиотеки и обратного преобразования выходных данных. Например, этот метод был написан для BLAKE3:
Этот метод принимает C-совместимую строку, её длину и указатель на выходную строку. Затем он преобразует C-совместимые входные данные в типы, которые используются основными методами библиотеки, и вызывает эти методы. После этого он должен преобразовать результаты работы библиотечных методов обратно в C-совместимый тип. В данном случае библиотека поддерживала прямую запись по указателю с помощью метода fill(), поэтому преобразование не потребовалось. Основная рекомендация здесь — создавать как можно меньше таких методов, чтобы при каждом вызове требовалось меньше преобразований и не возникало существенных накладных расходов.
Стоит отметить, что атрибут #[no_mangle] и extern "C" обязательны для всех подобных методов. Без них невозможно выполнить корректную C/C++-совместимую компиляцию. Более того, они необходимы для следующего шага интеграции.
После написания кода для shim-методов необходимо подготовить заголовочный файл для библиотеки. Это можно сделать вручную или использовать библиотеку cbindgen для автоматической генерации. В случае использования cbindgen потребуется написать build-скрипт build.rs и подключить cbindgen как build-зависимость.
Пример build-скрипта, который может автоматически сгенерировать заголовочный файл:
Также следует использовать атрибут #[no_mangle] и extern "C" для каждого C-совместимого элемента. В противном случае библиотека может быть собрана некорректно, а cbindgen не сможет автоматически сгенерировать заголовочный файл.
После выполнения всех этих шагов вы можете протестировать свою библиотеку в небольшом проекте, чтобы выявить все проблемы с совместимостью или генерацией заголовков. Если при генерации заголовков возникают какие-либо проблемы, вы можете попробовать настроить её с помощью файла cbindgen.toml (шаблон можно найти здесь: https://github.com/eqrion/cbindgen/blob/master/template.toml).
Стоит отметить проблему, возникшую при интеграции BLAKE3: MemorySanitizer может выдавать ложноположительные срабатывания, так как он не в состоянии определить, инициализированы ли некоторые переменные в Rust или нет. Это было решено написанием метода с более явным определением некоторых переменных, хотя такая реализация метода работает медленнее и используется только для исправления сборок с MemorySanitizer.