Что такое FFI
FFI (Foreign Function Interface) позволяет Rust вызывать код на других языках и вызываться из других языков. Чаще всего используется для вызова C-библиотек.
▸Основы FFI
1// Объявление внешней функции2extern "C" {3 fn strlen(s: *const std::ffi::c_char) -> usize;4}56fn main() {7 let c_str = std::ffi::CString::new("hello").unwrap();89 unsafe {10 let len = strlen(c_str.as_ptr());11 println!("Length: {}", len);12 }13}
Безопасные обёртки
▸Создание safe wrapper
1use std::ffi::{CStr, CString};2use std::os::raw::c_char;34// Внешняя функция5extern "C" {6 fn strlen(s: *const c_char) -> usize;7}89// Безопасная обёртка10fn safe_strlen(s: &str) -> usize {11 let c_str = CString::new(s).expect("CString::new failed");1213 unsafe { strlen(c_str.as_ptr()) }14}1516fn main() {17 let len = safe_strlen("hello");18 println!("Length: {}", len);19}
Bindgen
▸Установка
1cargo install bindgen-cli
▸Генерация привязок
1bindgen input.h -o bindings.rs
▸build.rs для автоматической генерации
1fn main() {2 println!("cargo:rerun-if-changed=wrapper.h");34 let bindings = bindgen::Builder::default()5 .header("wrapper.h")6 .parse_callbacks(Box::new(bindgen::CargoCallbacks))7 .generate()8 .expect("Unable to generate bindings");910 let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());11 bindings12 .write_to_file(out_path.join("bindings.rs"))13 .unwrap();14}
Классические примеры
▸Вызов libc
1use std::ffi::CString;2use std::os::raw::c_char;34fn main() {5 let c_str = CString::new("Hello, world!").unwrap();67 unsafe {8 libc::printf(c_str.as_ptr() as *const c_char);9 }10}
▸Вызов SQLite
1use rusqlite::{Connection, Result};23fn main() -> Result<()> {4 let conn = Connection::open("my_database.db")?;56 conn.execute(7 "CREATE TABLE IF NOT EXISTS users (8 id INTEGER PRIMARY KEY,9 name TEXT NOT NULL10 )",11 (),12 )?;1314 conn.execute(15 "INSERT INTO users (name) VALUES (?1)",16 ["Alice"],17 )?;1819 let mut stmt = conn.prepare("SELECT id, name FROM users")?;20 let users = stmt.query_map([], |row| {21 Ok((row.get(0)?, row.get(1)?))22 })?;2324 for user in users {25 println!("User: {:?}", user?);26 }2728 Ok(())29}
unsafe Rust
▸Когда нужен unsafe
1// 1. Вызов FFI функций2unsafe { strlen(ptr) }34// 2. Разыменование raw pointer5let x = 5;6let r = &x as *const i32;7unsafe { println!("{}", *r); }89// 3. Доступ к mutable static10static mut COUNTER: u32 = 0;1112unsafe {13 COUNTER += 1;14 println!("{}", COUNTER);15}1617// 4. Реализация unsafe trait18unsafe trait Foo {19 fn foo(&self);20}2122// 5. Доступ к полям union23union MyUnion {24 i: i32,25 f: f32,26}
▸Правила unsafe
1// unsafe не отключает проверкуborrow checker2// unsafe позволяет:3// - Разыменовывать raw pointers4// - Вызывать unsafe функции5// - Доступ к mutable static6// - Реализовывать unsafe traits78// Каждый unsafe блок должен иметь комментарий9unsafe {10 // SAFETY: pointer is valid and aligned11 *ptr = 5;12}
Связывание с CMake
▸cmake-build
1// build.rs2fn main() {3 let dst = cmake::build_new("my_c_lib");45 println!("cargo:rustc-link-search=native={}/lib", dst.display());6 println!("cargo:rustc-link-lib=static=my_c_lib");7 println!("cargo:rustc-link-lib=dylib=stdc++");8}
Примеры библиотек
▸OpenSSL
1use openssl::ssl::{SslMethod, SslConnector};2use std::net::TcpStream;34fn main() {5 let connector = SslConnector::new(SslMethod::tls()).unwrap();67 let stream = TcpStream::connect("example.com:443").unwrap();8 let mut stream = connector.connect("example.com", stream).unwrap();910 use std::io::Write;11 stream.write_all(b"GET / HTTP/1.112Host: example.com1314").unwrap();1516 use std::io::Read;17 let mut response = String::new();18 stream.read_to_string(&mut response).unwrap();19 println!("{}", response);20}
Тестирование FFI
1#[cfg(test)]2mod tests {3 use super::*;45 #[test]6 fn test_safe_strlen() {7 assert_eq!(safe_strlen(""), 0);8 assert_eq!(safe_strlen("hello"), 5);9 assert_eq!(safe_strlen("rust"), 4);10 }11}
Безопасность
▸Проблемы FFI
▸Решения
1// Всегда проверяйте указатели2unsafe {3 if ptr.is_null() {4 return Err(Error::NullPointer);5 }6 // Используйте ptr::read/write вместо *ptr7 let value = ptr::read(ptr);8}
Заключение
FFI позволяет использовать богатую экосистему C-библиотек в Rust. Важно создавать безопасные обёртки и минимизировать unsafe блоки. На собеседовании спрашивают про безопасность FFI, как создавать обёртки и когда использовать unsafe.