Перейти к основному содержимому
Перейти к основному содержимому

Rust Библиотеки

Интеграция библиотек Rust будет описана на основе интеграции хэш-функции BLAKE3.

Первым шагом интеграции является добавление библиотеки в папку /rust. Для этого необходимо создать пустой проект Rust и включить требуемую библиотеку в Cargo.toml. Также необходимо настроить компиляцию новой библиотеки как статической, добавив crate-type = ["staticlib"] в Cargo.toml.

Далее необходимо связать библиотеку с CMake с помощью библиотеки Corrosion. Первым шагом является добавление папки библиотеки в CMakeLists.txt внутри папки /rust. После этого следует добавить файл CMakeLists.txt в директорию библиотеки. В нем необходимо вызвать функцию импорта Corrosion. Эти строки использовались для импорта BLAKE3:

corrosion_import_crate(MANIFEST_PATH Cargo.toml NO_STD)

target_include_directories(_ch_rust_blake3 INTERFACE include)
add_library(ch_rust::blake3 ALIAS _ch_rust_blake3)

Таким образом, мы создадим корректную цель CMake с помощью Corrosion, а затем переименуем ее в более удобное имя. Обратите внимание, что имя _ch_rust_blake3 берется из Cargo.toml, где оно используется как имя проекта (name = "_ch_rust_blake3").

Поскольку типы данных Rust несовместимы с типами данных C/C++, мы будем использовать наш пустой библиотечный проект для создания методов-оболочек (shim methods) для преобразования данных, полученных от C/C++, вызова библиотечных методов и обратного преобразования для выходных данных. Например, этот метод был написан для BLAKE3:

#[no_mangle]
pub unsafe extern "C" fn blake3_apply_shim(
    begin: *const c_char,
    _size: u32,
    out_char_data: *mut u8,
#[no_mangle]
pub unsafe extern "C" fn blake3_apply_shim(
    begin: *const c_char,
    _size: u32,
    out_char_data: *mut u8,
) -> *mut c_char {
    if begin.is_null() {
        let err_str = CString::new("input was a null pointer").unwrap();
        return err_str.into_raw();
    }
    let mut hasher = blake3::Hasher::new();
    let input_bytes = CStr::from_ptr(begin);
    let input_res = input_bytes.to_bytes();
    hasher.update(input_res);
    let mut reader = hasher.finalize_xof();
    reader.fill(std::slice::from_raw_parts_mut(out_char_data, blake3::OUT_LEN));
    std::ptr::null_mut()
}

Этот метод получает C-совместимую строку, ее размер и указатель на выходную строку в качестве входных данных. Затем он преобразует C-совместимые входные данные в типы, которые используются фактическими методами библиотеки, и вызывает их. После этого необходимо преобразовать выходные данные библиотечных методов обратно в C-совместимый тип. В данном конкретном случае библиотека поддерживала прямую запись в указатель с помощью метода fill(), поэтому преобразование не потребовалось. Главный совет здесь — создавать меньше методов, чтобы вам понадобилось меньше преобразований при каждом вызове метода и не создавалось значительных накладных расходов.

Стоит отметить, что атрибут #[no_mangle] и extern "C" обязательны для всех таких методов. Без них не получится выполнить корректную компиляцию, совместимую с C/C++. Более того, они необходимы для следующего шага интеграции.

После написания кода для методов-оболочек, нам необходимо подготовить заголовочный файл для библиотеки. Это можно сделать вручную или использовать библиотеку cbindgen для автогенерации. В случае использования cbindgen вам нужно будет написать скрипт сборки build.rs и включить cbindgen как зависимость для сборки.

Пример скрипта сборки, который может автоматически генерировать заголовочный файл:

let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();

let package_name = env::var("CARGO_PKG_NAME").unwrap();
let output_file = ("include/".to_owned() + &format!("{}.h", package_name)).to_string();

match cbindgen::generate(&crate_dir) {
    Ok(header) => {
        header.write_to_file(&output_file);
    }
    Err(err) => {
        panic!("{}", err)
    }
}

Также вам следует использовать атрибуты #[no_mangle] и extern "C" для каждого C-совместимого атрибута. Без этого библиотека может скомпилироваться некорректно, и cbindgen не запустит автогенерацию заголовка.

После выполнения всех этих шагов вы можете протестировать вашу библиотеку в небольшом проекте, чтобы выявить все проблемы с совместимостью или генерацией заголовков. Если возникают какие-либо проблемы во время генерации заголовков, вы можете попробовать настроить их с помощью файла cbindgen.toml (шаблон можно найти здесь: https://github.com/eqrion/cbindgen/blob/master/template.toml).

Стоит отметить проблему, которая возникла при интеграции BLAKE3: MemorySanitizer может вызывать ложнопозитивные отчеты, поскольку он не может увидеть, инициализированы ли некоторые переменные в Rust или нет. Это было решено написанием метода с более явным определением для некоторых переменных, хотя эта реализация метода медленнее и используется только для устранения проблем с компиляцией MemorySanitizer.