FFI (Foreign Function Interface)
Ящики, позволяющие Rust взаимодействовать с кодом, написанным на других языках.
crates/section-ffi
|
|
|
Официальные материалы Rust
C/Rust interaction
📌 Working with C unions in Rust FFI
Инструменты
- bindgen: rust-bindgen (генерация
.rs файлов из C)
- cbindgen (генерация
.h файлов из Rust)cbindgen
- cc crate: crates/cc (компиляция C библиотеки и линковка к Rust)
|
|
|
Чтобы Rust линковал C библиотеку (через build.rs):
- Вариант 1: Классическая статическая библиотека .a (с ar вручную)
- Вариант 2: сборка C библиотеки через crate cc который сам соберёт
- Вариант 3: Дать Rust’у готовую динамическую библиотеку .so
|
Файл lib_ffi/build.rs
Вариант 1: Классическая статическая библиотека .a (с ar вручную)
Скопировать библиотеку libfooclib.a в папку Rust-проекта, где будет линковаться
libfooclib.a → static=fooclib
lib_ffi/build.rs:
fn main() {
// Rust ищет lib + NAME + .a
// Путь, где лежит libfooclib.a (от корня проекта)
let dir = concat!(env!("CARGO_MANIFEST_DIR"), "/../library_c");
println!("cargo:rustc-link-search=native={dir}");
println!("cargo:rustc-link-lib=static=fooclib");// libfooclib.a -> fooclib
}
Для проверки std приложения
cargo run --package app_std
Вариант 2: сборка C библиотеки через crate cc который сам соберёт
Cargo сам вызовет:
- gcc -c …
- ar rcs libfooclib.a …
- И положит libfooclib.a в target/debug/build/.../out/
Cargo.toml:
[build-dependencies]
cc = "1.0"
lib_ffi/build.rs:
fn main() {
cc::Build::new()
.file("../library_c/lib.c") // путь к твоему lib.c
.include("../library_c") // путь к lib.h, если нужен
.compile("fooclib"); // создаст libfooclib.a
}
Вариант 3: Дать Rust’у готовый .so
Rust автоматически ищет файл в виде lib.so, поэтому dylib=fooclib → ищет libfooclib.so
libfooclib.so → dylib=fooclib
Линковщик при сборке нашёл libfooclib.so по -L, но при запуске бинарника Linux ищет .so по стандартным путям (/usr/lib, /lib) или тем, что указаны в переменной LD_LIBRARY_PATH.
Сейчас libfooclib.so лежит в library_c/, а бинарник в target/debug/ — Linux её просто не видит.
Вариант запуска 1:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/jeka/Projects/Rust/Rust_FFI/std_diff_no_std/library_c
cargo run --package app_std
Вариант запуска 2:
- скопировать .so в папку с бинарником: cp library_c/libfooclib.so target/debug/
cargo run --package app_std
fn main() {
let lib_dir = concat!(env!("CARGO_MANIFEST_DIR"), "/../library_c");
println!("cargo:rustc-link-search=native={}", lib_dir);// относительно директории target/debug/build/lib_ffi-xxxx/out
println!("cargo:rustc-link-lib=dylib=fooclib");// Rust ищет libfooclib.so
}
crate bindgen
- Использование автогенерации Rust кода на основе C кода из .h
- С вариантом 2: сборка C библиотеки через crate cc который сам соберёт
extern crate bindgen;
use std::env;
use std::path::PathBuf;
fn main() {
// Путь к C исходникам
let library_c_path = PathBuf::from("../library_c");
let lib_c_file = library_c_path.join("lib.c");
let lib_h_file = library_c_path.join("lib.h");
// --- CC: компилируем C код в статическую библиотеку ---
cc::Build::new()
.file(&lib_c_file)
.include(&library_c_path) // чтобы lib.c мог найти lib.h
.compile("fooclib"); // создаст libfooclib.a
// --- Bindgen: генерируем Rust биндинги ---
let bindings = bindgen::Builder::default()
.header(lib_h_file.to_str().unwrap()) // подключаем наш заголовок
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.allowlist_function("add") // генерируем только функцию add
.generate()
.expect("Не удалось сгенерировать биндинги");
// --- Записываем биндинги в $OUT_DIR/bindings.rs ---
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Не удалось записать биндинги");
// --- Link: указываем Rust линковщику статическую библиотеку ---
println!("cargo:rustc-link-lib=static=fooclib");
println!("cargo:rerun-if-changed={}", lib_c_file.display());
println!("cargo:rerun-if-changed={}", lib_h_file.display());
}
|
|
Name mangling (искажение имен)
|
Name mangling (искажение имен)
Это касается C++ в котором есть возможность использовать перегрузку, когда имена функций одинаковые. Тогда компоновщик применяет искажение имен функций и что бы их увидеть используют инструмент nm
ns1::add(int,int) -> __ZN3ns13addEii
ns1::add(long,long) -> __ZN3ns13addExx
nm ffi-lib.o | grep add # what the linker sees for C
0000000000000000 T _add
nm ffi-cpp-lib.o | grep add # what the linker sees for C++
0000000000000000 T __ZN3ns13addEii
nm ffi-cpp-lib.o | grep add | c++filt # use c++filt
0000000000000000 T ns1::add(int, int)
Rust FFI с C: используем extern "C" + #[no_mangle] → символы без mangling, linker просто соединяет, типы не проверяет.
То есть когда используешь Rust с C через FFI, важно следить, чтобы сигнатуры совпадали, иначе компоновщик пропустит ошибку, а краш будет уже во время работы.
|
|
|
Типы
На стороне Rust если есть выбор то, есть модуль std::os::raw, который определяет базовые C-подобные типы для FFI. Использование std::os::raw::{c_int, c_long, c_char, c_void} гарантирует, что Rust будет использовать тот же размер и представление, что и int в C на той же платформе.
А если есть выбор какой тип данных использовать в языке C то лучше использовать типы из библиотеки stdint.h, например uint32_t, int64_t и т.д., мы точно знаем размер типа: 32 бита, 64 бита и т.п.
crate libc нужен не для базовых типов которые есть в std::os::raw, а для большего покрытия C API и системных констант
- Структуры и типы, которых нет в std::os::raw
- Константы и макросы C
- Системные функции (libc даёт почти все POSIX/Unix функции: mmap, open, read, write, fork, getpid и т.д)
crate bindgen инструмент, который автоматически генерирует Rust FFI-код на основе C-заголовков (.h). Используется в build.rs
Выравнивание структуры #[repr(C)]
Для строк alloc::ffi::CString и alloc::ffi::CStr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Компиляция:
-
Прямая компиляция всех файлов (gcc main.c utils.c -o my_program.out) — это как cargo run без --release. Всё компилируется и сразу линкуется в один бинарник.
-
Компиляция отдельных объектных файлов (gcc -c ...) и последующая линковка — это то, что Rust делает под капотом с крейтами (.rlib/.a). Каждый .rs файл компилируется в объектный код, потом Cargo линковкой собирает всё вместе.
-
Динамическая линковка — Rust тоже умеет использовать cdylib или dylib для создания динамических библиотек, которые потом можно подключить к другим программам.
|
|
|
Возможность использовать Rust с FFI(Подключение внешних функций) может вызывать FFI и может быть вызываемым другим языком.
Возможность использовать преимущества Rust в языках где этого нет или слабо реализованно.
Rust идеальный вариант из-за отсутствия сборщика мусора и низком требовании к системе.
К примеру Rust может работать с числами парралельно к тому же они располагаются в стеке, а не в куче как в других языках ООП
|
|
|
|
|
|
- Обработка ошибок в FFI
- Принимающие строки
- Передача строк
|
|
|
Символы Rust по умолчанию искажают имена (как C++), поэтому определениям функций также нужен #[no_mangle] атрибут, чтобы гарантировать, что они доступны через простое имя. Это, в свою очередь, означает, что имя функции является частью единого глобального пространства имен, которое может конфликтовать с любым другим символом, определенным в программе. Таким образом, рассмотрите возможность использования префикса для открытых имен, чтобы избежать неоднозначностей ( mylib_...).
|
|
|
Устранение несоответствия ABI C
#![feature(repr_transparent)]
#[repr(transparent)]
struct Grams(f64);
#[repr(transparent)]
struct Millimeters(f64);
|
|
|
Существует ряд ящиков, которые позволяют удобно взаимодействовать с различными языками.
bindgen автоматически генерирует привязки Rust FFI к библиотекам C и C++
- C ++: cxx
- Python: inline-python,pyo3
- JS / DOM / WASM: wasm-bindgen (ничего не использовать wasm-pack), web-sys. rusty_v8.
- Java: j4rs, jni
|
|
|
Crate libc - необработанные привязки FFI к системным библиотекам платформ
#![feature(libc)]
extern crate libc;
use libc::pid_t;
#[link(name = "c")]
extern {
fn getpid() -> pid_t;
}
fn main() {
let x = unsafe { getpid() };
println!("Process PID is {}", x);
}
|
|
|
Для многих системных API-интерфейсов Nix предоставляет безопасную альтернативу небезопасным API-интерфейсам, предоставляемым ящиком libc
|
|
|
Инструмент bindgen может обрабатывать некоторые конструкции C++, но только подмножество и в ограниченном виде. Для лучшей (но все еще несколько ограниченной) интеграции рассмотрите возможность использования cxx Crate для взаимодействия C++/Rust. Вместо генерации кода Rust из объявлений C++, cxx использует подход автоматической генерации кода Rust и C++ из общей схемы, что обеспечивает более тесную интеграцию.
|
|
|
Поддержание идеальной синхронизации C и Rust кажется хорошей целью для автоматизации, и проект Rust предоставляет подходящий инструмент для этой работы: bindgen.
Автоматическая генерация декларации Rust из декларации C гарантирует, что они будут синхронизированы.
Основная функция bindgen — анализ заголовочного файла C и выдача соответствующих объявлений Rust.
Возьмем некоторые примеры объявлений C:
/* File lib.h */
#include <stdint.h>
typedef struct {
uint8_t byte;
uint32_t integer;
} FfiStruct;
int add(int x, int y);
uint32_t add32(uint32_t x, uint32_t y);
Инструмент bindgen можно вызвать вручную (или вызвать с помощью build.rs скрипт сборки) для создания соответствующего файла Rust:
% bindgen --no-layout-tests \
--allowlist-function="add.*" \
--allowlist-type=FfiStruct \
-o src/generated.rs \
lib.h
Сгенерированный Rust идентичен созданным вручную:
/* automatically generated by rust-bindgen 0.59.2 */
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct FfiStruct {
pub byte: u8,
pub integer: u32,
}
extern "C" {
pub fn add(
x: ::std::os::raw::c_int,
y: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn add32(x: u32, y: u32) -> u32;
}
И может быть добавлен в код Rust с помощью исходного уровня include! макрос :
// Include the auto-generated Rust declarations.
include!("generated.rs");
|
|
|
-
примитивные типы Rust usize и isize имеют такое же представление, как типы C size_t и ptrdiff_t ;
-
указатели C и C++ и ссылки C++ соответствуют типам простых указателей Rust, *mut T и *const T ;
use std::os::raw::c_char;
extern {
fn strlen(s: *const c_char) -> usize;
}
Имея такой блок extern, мы можем вызывать strlen, как любую другую функцию Rust, хотя тип выдает в ней туриста:
use std::ffi::CString;
let rust_str = "I'll be back";
let null_terminated = CString::new(rust_str).unwrap();
unsafe {
assert_eq!(strlen(null_terminated.as_ptr()), 12);
}
|
|
|
Rust по умолчанию не использует строки с завершающим нулем.
Вместо этого он использует толстые указатели (&str) или структуры (String структура потому что реализована как структура содержащая Vec<u8>), размещенные в куче, для непосредственного хранения информации о длине.
Хотя при желании мы можем работать и со строками, оканчивающимися нулем.
Это особенно полезно при работе с библиотеками C в контексте FFI.
Для этой цели есть два типа. std::ffi::CStr и std::ffi::CString.
Документация по Rust довольно хороша, если вы хотите узнать о них больше.
Создания C-совместимых строк, которые используются в Foreign Function Interface (FFI)
C string - создаёт статический массив байтов, который всегда оканчивается нулевым байтом (\0). Этот нулевой байт — это так называемый null-terminator, который служит признаком конца строки в языке C. Обычные строки в Rust не имеют такого завершающего символа, поэтому при передаче в C-функции без \0 может произойти чтение некорректных данных или краш программы.
use std::ffi::CStr;
// Обычная C-функция, которая принимает C-строку.
// #[link(name = "my_c_lib")]
// extern "C" {
// fn print_from_c(s: *const i8);
// }
fn main() {
let c_str_literal = c"hello"; // Тип &CStr
// print_from_c(c_str_literal.as_ptr());
println!("{:?}", c_str_literal); // Выведет CStr::from_bytes_with_nul(...)
}
// "raw" C string. Эта форма используется, когда в строке есть символы, которые могли бы быть восприняты как escape-последовательности, например, \n, \t или \.
use std::ffi::CStr;
fn main() {
let path_str = cr#"C:\Users\John\file.txt"#; // Обычный c"\t" стал бы символом табуляции
println!("{:?}", path_str);
}
|
|
|
| Тип С | Соответствующий тип в std::os::raw |
short | c_short |
int | c_int |
long | c_long |
long long | c_longlong |
unsigned short | c_ushort |
unsigned, unsigned int | c_uint |
unsigned long | c_ulong |
unsigned long long | c_ulonglong |
char | c_char |
signed char | c_schar |
unsigned char | c_uchar |
float | c_float |
double | c_double |
void *, const void * | *mut c_void, *const c_void |
|
|
Предварительная сборка
tooling-directives
|
Cargo поддерживает выполнение кода во время сборки, предоставляя файл build.rs на верхнем уровне, содержащий соответствующие функции.
Это может запускать произвольный код и включает возможность генерировать *.rs файлы, которые будут включены в текущую сборку ящика.
Это неудобный способ организовать генерацию кода во время сборки, потому что Rust не идеальный язык для написания правил сборки (хотя он может стать хорошим языком для генерации кода Rust).
build.rs может быть лучшим выбором, если вам нужна очень портативная генерация кода во время сборки, поскольку она не полагается ни на что, кроме системы Rust, от которой вы все равно зависели.
Файл .build.rs
// build.rs (sample pre-build script)
fn main() {
// You need to rely on env. vars for target; `#[cfg(...)]` are for host.
let target_os = env::var("CARGO_CFG_TARGET_OS");
}
|
|
|
use std::fmt;
// этот внешний блок ссылается на библиотеку libm
#[link(name = "m")]
extern {
// Это внешняя функция
// которая вычисляет квадратный корень комплексного числа одинарной точности
fn csqrtf(z: Complex) -> Complex;
fn ccosf(z: Complex) -> Complex;
}
// Поскольку вызов внешних функций считается небезопасным,
// для них часто пишут безопасные обёртки.
fn cos(z: Complex) -> Complex {
unsafe { ccosf(z) }
}
fn main() {
// z = -1 + 0i
let z = Complex { re: -1., im: 0. };
// вызов внешней функции — небезопасная операция
let z_sqrt = unsafe { csqrtf(z) };
println!("the square root of {:?} is {:?}", z, z_sqrt);
// вызов безопасного API, обернутого вокруг небезопасной операции
println!("cos({:?}) = {:?}", z, cos(z));
}
// Минимальная реализация комплексных чисел одинарной точности
#[repr(C)]
#[derive(Clone, Copy)]
struct Complex {
re: f32,
im: f32,
}
impl fmt::Debug for Complex {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.im < 0. {
write!(f, "{}-{}i", self.re, -self.im)
} else {
write!(f, "{}+{}i", self.re, self.im)
}
}
}
|
|
|
use std::thread;
#[no_mangle]
pub extern fn process() {
let handles: Vec<_> = (0..10).map(|_| {
thread::spawn(|| {
let mut x = 0;
for _ in 0..5_000_000 {
x += 1
}
x
})
}).collect();
for h in handles {
println!("Поток завершился со счётом={}",
h.join().map_err(|_| "Не удалось соединиться с потоком!").unwrap());
}
}
Где :
#[no_mangle] - не дает Rust компилятору изменить имя функции
pub - вызов ф-ции за пределами модуля
extern - возможность вызвать из C языка
В Cargo.toml добавим что скажет компилятору скомпилировать не rlib а в динамическую библиотеку dylib
[lib]
name = "embed"
crate-type = ["dylib"]
Выполнив сборку $cargo build --release
Получим исполняемый файл ibembed.so или embed.dll или libembed.dylib
К примеру для использования в Node.js:
скачаем $ npm install fii
javascript:
var ffi = require('ffi'); // подключение к библиотеки ffi
2. var lib = fii.Library('target/release/libembed',{ 'process':['void',[]]}); // загрузка библиотеки Rust т.е. ее функции process()
3. lib.process(); // вызов загруженной функции без аргументов так как стоит [] и без возвращаемого значения так как стоит 'void'
|
|
|
Стандартная библиотека Rust (std) компилируется только для нескольких архитектур. Итак, чтобы скомпилировать другие архитектуры (которые поддерживает LLVM), программы Rust могут отказаться от использования всего std и вместо этого использовать только переносное подмножество, известное как The Core Library (core).
#![feature(start, libc, lang_items)]
#![no_std]
#![no_main]
// The libc crate allows importing functions from C.
extern crate libc;
// A list of C functions that are being imported
extern {
pub fn printf(format: *const u8, ...) -> i32;
}
#[no_mangle]
// The main function, with its input arguments ignored, and an exit status is returned
pub extern fn main(_nargs: i32, _args: *const *const u8) -> i32 {
// Print "Hello, World" to stdout using printf
unsafe {
printf(b"Hello, World!\n" as *const u8);
}
// Exit with a return status of 0.
0
}
#[lang = "eh_personality"] extern fn eh_personality() {}
#[lang = "panic_fmt"] extern fn panic_fmt() -> ! { panic!() }
|
|
Rust to C
Связующая логистика
cargo::rustc-link-lib=LIB
rustc-link-lib
linking-logistics
|
Связующая логистика
#[link(name = "cffi")] // Необходима внешняя библиотека типа `libcffi.a`
extern "C" {
// ...
}
Или через скрипт сборки, который выдает cargo:rustc-link-lib инструкцию cargo: 2
// File build.rs
fn main() {
// Необходима внешняя библиотека типа `libcffi.a`
println!("cargo:rustc-link-lib=cffi");
}
// File build.rs
fn main() {
// ...
// из `Cargo.toml`
let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
// Найдите собственные библиотеки на один каталог выше.
println!(
"cargo:rustc-link-search=native={}",
std::path::Path::new(&dir).join("..").display()
);
}
// Определение структуры данных C.
// Изменения здесь должны быть отражены в lib.rs.
typedef struct {
uint8_t byte;
uint32_t integer;
} FfiStruct;
// Аналогичная структура данных Rust.
// Изменения здесь должны быть отражены в lib.h / lib.c.
#[repr(C)]
pub struct FfiStruct {
pub byte: u8,
pub integer: u32,
}
|
|
1. Rust to C++
Передача mut ссылки на массив/Vec
|
extern crate libc;
use libc::size_t;
use std::fs::File;
use std::io::Read;
// Обьявление C функций
extern {
fn write_easy(content:*mut u8,count_buff:libc::size_t);
}
const COUNT_BUFF:usize = 10;
fn write(){
let content_ptr: *mut u8 = std::ptr::null_mut();
// used Vec
if true {
let mut content: Vec =Vec::with_capacity(COUNT_BUFF);
for i in 0..COUNT_BUFF{
content.push(i as u8);
}
println!("Исходный массив:");
for i in 0..COUNT_BUFF{
println!("value={:?}",content[i]);
}
let content_ptr: *mut u8 = content.as_mut_ptr();
unsafe{
println!("\nИзменим массив (из ptr):");
for i in 0..COUNT_BUFF{
std::ptr::write(content_ptr.offset(i as isize), 12 + i as u8);
println!("addr={:p}, value={}",content_ptr,std::ptr::read(content_ptr.offset(i as isize)));
}
write_easy(content_ptr,COUNT_BUFF);
}
println!("\nИзменения из FFI:");
for i in 0..COUNT_BUFF{
println!("addr={:p}, value={:?}",content_ptr,content[i]);
}
}
// used Array
if false {
let mut content:[u8;COUNT_BUFF]=[0;COUNT_BUFF];
for i in 0..COUNT_BUFF{
content[i] = i as u8;
}
println!("Исходный массив:");
for i in 0..COUNT_BUFF{
println!("value={:?}",content[i]);
}
let content_ptr: *mut u8 = &mut content[0] as *mut u8;
unsafe{
println!("\nИзменим массив (через ptr):");
for i in 0..COUNT_BUFF{
std::ptr::write(content_ptr.offset(i as isize), 12 + i as u8);
println!("addr={:p}, value={}",content_ptr,std::ptr::read(content_ptr.offset(i as isize)));
}
write_easy(content_ptr,COUNT_BUFF);
}
println!("\nИзменения из FFI:");
for i in 0..COUNT_BUFF{
println!("addr={:p}, value={:?}",content_ptr,content[i]);
}
}
// used slice
if true {
let mut content: Vec =Vec::with_capacity(COUNT_BUFF);
for i in 0..COUNT_BUFF{
content.push(i as u8);
}
unsafe fn wrap(src:&[u8]){
write_easy(src.as_ptr() as *mut u8, src.len() as size_t);
}
unsafe {
wrap(&content);
}
}
}
fn read() -> std::io::Result<()> {
let mut f = File::open("src/OUT.raw").unwrap();//открыть только для чтения
let mut buffer:std::vec::Vec = Vec::with_capacity(50);
f.read_to_end(&mut buffer)?;
println!("Получил обратно массив (через файл):");
println!("{:#?}",buffer);
Ok(())
}
fn main() {
write();
read();
}
Вывод
Вывод:
Исходный массив:
value=0
value=1
value=2
value=3
value=4
value=5
value=6
value=7
value=8
value=9
Изменим массив (из ptr):
addr=0x555786d07ad0, value=12
addr=0x555786d07ad0, value=13
addr=0x555786d07ad0, value=14
addr=0x555786d07ad0, value=15
addr=0x555786d07ad0, value=16
addr=0x555786d07ad0, value=17
addr=0x555786d07ad0, value=18
addr=0x555786d07ad0, value=19
addr=0x555786d07ad0, value=20
addr=0x555786d07ad0, value=21
Пришел массив с++:
addr=0x555786d07ad0, value=12
addr=0x555786d07ad1, value=13
addr=0x555786d07ad2, value=14
addr=0x555786d07ad3, value=15
addr=0x555786d07ad4, value=16
addr=0x555786d07ad5, value=17
addr=0x555786d07ad6, value=18
addr=0x555786d07ad7, value=19
addr=0x555786d07ad8, value=20
addr=0x555786d07ad9, value=21
Изменения из FFI:
addr=0x555786d07ad0, value=13
addr=0x555786d07ad0, value=14
addr=0x555786d07ad0, value=15
addr=0x555786d07ad0, value=16
addr=0x555786d07ad0, value=17
addr=0x555786d07ad0, value=18
addr=0x555786d07ad0, value=19
addr=0x555786d07ad0, value=20
addr=0x555786d07ad0, value=21
addr=0x555786d07ad0, value=22
Получил обратно массив:
[
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
]
|
|
2. Rust to C++
Передача mut ссылки на массив/Vec
|
File minim.cpp:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
extern "C"
void write_easy(uint8_t *content,size_t count_buff){
printf("\nПришел массив с++:\n");
for (int i=0;i<count_buff;i++){
printf("addr=%p, value=%d\n",content+i,*(content+i));
content[i]++;
}
FILE *fp;
if ((fp=fopen("/media/jeka/PROJECTS/Develop/project_rust/rust-ffi-examples/test_rust_to_cpp/src/OUT.raw", "w"))==NULL) {
printf ("Cannot open file.\n");
exit(1);
}
fwrite( (char*)content, sizeof(uint8_t),count_buff ,fp);
fclose(fp);
}
File Cargo.toml:
[package]
name = "test_rust_to_cpp"
version = "0.1.0"
edition = "2021"
build = "build.rs"
[dependencies]
libc = {version = "0.2", features=["std"]}
[build-dependencies]
cc = "1.0"
File build.rs:
extern crate cc;
fn main() {
cc::Build::new()
.file("src/minim.cpp")
.cpp(true)
.compile("libtriple.a");
}
|
|
|
Представьте, что вы используете библиотеку C в своем проекте Rust. Допустим, вы получаете буфер данных из кода C в виде указателя *const u8 и длины usize. Допустим, вы хотели бы передать данные по слою логики ржавчины, возможно, изменив их (заставляет ли это вас задуматься Cow?)
use std::borrow::{Borrow, Cow};
use std::fmt::{Debug, Formatter};
use std::ops::Deref;
struct NativeBuffer {
pub ptr: *const u8,
pub len: usize
}
impl Borrow<[u8]> for NativeBuffer {
fn borrow(&self) -> &[u8] {
unsafe {
std::slice::from_raw_parts(self.ptr, self.len)
}
}
}
impl Deref for NativeBuffer {
type Target = [u8];
fn deref(&self) -> &Self::Target {
self.borrow()
}
}
impl Debug for NativeBuffer {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let data: &[u8] = self.borrow();
write!(f, "NativeBuffer {{ data: {:?}, len: {} }}", data, self.len)
}
}
#[derive(Debug)]
struct OwnedBuffer {
owned_data: Vec,
native_proxy: NativeBuffer,
}
impl ToOwned for NativeBuffer {
type Owned = OwnedBuffer;
fn to_owned(&self) -> OwnedBuffer {
let slice: &[u8] = self.borrow();
let owned_data = slice.to_vec();
let native_proxy = NativeBuffer {
ptr: owned_data.as_ptr(),
len: owned_data.len()
};
OwnedBuffer {
owned_data,
native_proxy,
}
}
}
impl Borrow for OwnedBuffer {
fn borrow(&self) -> &NativeBuffer {
&self.native_proxy
}
}
impl OwnedBuffer {
pub fn append(&mut self, data: &[u8]) {
self.owned_data.extend(data);
self.native_proxy = NativeBuffer {
ptr: self.owned_data.as_ptr(),
len: self.owned_data.len()
};
}
}
fn main() {
// Simulates the data coming across FFI (from C)
let data = vec![1, 2, 3];
let ptr = data.as_ptr();
let len = data.len();
let native_buffer = NativeBuffer { ptr, len};
let mut buffer = Cow::Borrowed(&native_buffer);
// NativeBuffer { data: [1, 2, 3], len: 3 }
println!("{:?}", buffer);
// No data cloned
assert_eq!(buffer.ptr, ptr);
assert_eq!(buffer.len, len);
if buffer.len > 1 {
buffer.to_mut().append(&[4, 5, 6]);
// OwnedBuffer { owned_data: [1, 2, 3, 4, 5, 6], native_proxy: NativeBuffer { data: [1, 2, 3, 4, 5, 6], len: 6 } }
println!("{:?}", buffer);
// Data is cloned
assert_ne!(buffer.ptr, ptr);
assert_eq!(buffer.len, len + 3);
}
let slice: &[u8] = &buffer;
// [1, 2, 3, 4, 5, 6]
println!("{:?}", slice);
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|