C interop best practices
Foreign Function Interface (FFI) allows Rust code to interact with code written in other languages, primarily C. FFI boundaries are the interfaces where Rust's safety guarantees meet the unsafe world of foreign code. Properly designed FFI boundaries ensure that unsafe foreign interactions don't compromise Rust's memory safety.
FFI is essential for:
When crossing FFI boundaries, you face several challenges:
Without careful design, FFI code can introduce memory leaks, data races, use-after-free bugs, and other memory safety violations.
use std::os::raw::{c_char, c_int, c_void};
// Declare external C functions
extern "C" {
// Standard C library functions
fn strlen(s: *const c_char) -> usize;
fn strcmp(s1: *const c_char, s2: *const c_char) -> c_int;
fn malloc(size: usize) -> *mut c_void;
fn free(ptr: *mut c_void);
// Custom C library
fn compute_hash(data: *const u8, len: usize) -> u64;
fn process_array(arr: *mut i32, len: usize) -> c_int;
}
// Safe wrapper around strlen
fn safe_strlen(s: &str) -> usize {
// Convert Rust string to C string
let c_str = std::ffi::CString::new(s).expect("CString::new failed");
unsafe {
// SAFETY: c_str.as_ptr() returns a valid null-terminated string
strlen(c_str.as_ptr())
}
}
// Safe wrapper around strcmp
fn safe_strcmp(s1: &str, s2: &str) -> std::cmp::Ordering {
let c_str1 = std::ffi::CString::new(s1).expect("CString::new failed");
let c_str2 = std::ffi::CString::new(s2).expect("CString::new failed");
unsafe {
// SAFETY: Both pointers are valid null-terminated strings
let result = strcmp(c_str1.as_ptr(), c_str2.as_ptr());
use std::cmp::Ordering;
match result {
0 => Ordering::Equal,
x if x < 0 => Ordering::Less,
_ => Ordering::Greater,
}
}
}
fn example_basic_ffi() {
let s = "Hello, FFI!";
let len = safe_strlen(s);
println!("Length of '{}': {}", s, len);
let s1 = "apple";
let s2 = "banana";
println!("Comparing '{}' and '{}': {:?}", s1, s2, safe_strcmp(s1, s2));
}
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
extern "C" {
fn get_version() -> *const c_char;
fn set_name(name: *const c_char) -> bool;
fn get_error_message() -> *mut c_char;
fn free_string(s: *mut c_char);
}
/// Safe wrapper that converts C string to Rust String
fn safe_get_version() -> Option<String> {
unsafe {
// SAFETY: Assuming get_version returns a valid static string or null
let ptr = get_version();
if ptr.is_null() {
return None;
}
// Convert C string to Rust string
let c_str = CStr::from_ptr(ptr);
c_str.to_str().ok().map(|s| s.to_owned())
}
}
/// Safe wrapper that converts Rust string to C string
fn safe_set_name(name: &str) -> Result<(), Box<dyn std::error::Error>> {
// CString automatically adds null terminator
let c_name = CString::new(name)?;
unsafe {
// SAFETY: c_name.as_ptr() is valid for the duration of this call
let success = set_name(c_name.as_ptr());
if success {
Ok(())
} else {
Err("Failed to set name".into())
}
}
}
/// Wrapper for C function that allocates a string (caller must free)
fn safe_get_error_message() -> Option<String> {
unsafe {
// SAFETY: Assuming function returns null or a valid malloc'd string
let ptr = get_error_message();
if ptr.is_null() {
return None;
}
// Convert to CStr first
let c_str = CStr::from_ptr(ptr);
let result = c_str.to_str().ok().map(|s| s.to_owned());
// Free the C-allocated string
free_string(ptr);
result
}
}
fn example_string_handling() {
if let Some(version) = safe_get_version() {
println!("Version: {}", version);
}
if let Err(e) = safe_set_name("RustApp") {
eprintln!("Error setting name: {}", e);
}
if let Some(error) = safe_get_error_message() {
eprintln!("C library error: {}", error);
}
}
use std::os::raw::{c_int, c_double};
// C-compatible struct with #[repr(C)]
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct Point {
pub x: c_double,
pub y: c_double,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct Rect {
pub top_left: Point,
pub bottom_right: Point,
}
#[repr(C)]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
extern "C" {
fn calculate_distance(p1: Point, p2: Point) -> c_double;
fn rect_contains_point(rect: *const Rect, point: Point) -> bool;
fn create_gradient(start: Color, end: Color, steps: c_int) -> *mut Color;
fn free_colors(colors: *mut Color);
}
impl Point {
pub fn new(x: f64, y: f64) -> Self {
Point { x, y }
}
pub fn distance_to(&self, other: &Point) -> f64 {
unsafe {
// SAFETY: Point is repr(C) and both are valid
calculate_distance(*self, *other)
}
}
}
impl Rect {
pub fn new(x1: f64, y1: f64, x2: f64, y2: f64) -> Self {
Rect {
top_left: Point::new(x1, y1),
bottom_right: Point::new(x2, y2),
}
}
pub fn contains(&self, point: &Point) -> bool {
unsafe {
// SAFETY: self is repr(C) and we pass a valid reference
rect_contains_point(self as *const Rect, *point)
}
}
}
impl Color {
pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
Color { r, g, b, a }
}
pub fn gradient(start: Color, end: Color, steps: usize) -> Vec<Color> {
unsafe {
// SAFETY: Assuming function returns valid array of 'steps' colors
let ptr = create_gradient(start, end, steps as c_int);
if ptr.is_null() {
return Vec::new();
}
// Copy colors to Rust Vec
let colors = std::slice::from_raw_parts(ptr, steps).to_vec();
// Free C-allocated memory
free_colors(ptr);
colors
}
}
}
fn example_struct_passing() {
let p1 = Point::new(0.0, 0.0);
let p2 = Point::new(3.0, 4.0);
let distance = p1.distance_to(&p2);
println!("Distance: {}", distance);
let rect = Rect::new(0.0, 0.0, 10.0, 10.0);
let point = Point::new(5.0, 5.0);
println!("Contains: {}", rect.contains(&point));
let start = Color::new(255, 0, 0, 255);
let end = Color::new(0, 0, 255, 255);
let gradient = Color::gradient(start, end, 10);
println!("Gradient steps: {}", gradient.len());
}
use std::os::raw::{c_int, c_void};
use std::ffi::c_void as ffi_void;
// Type alias for C callback
type Callback = extern "C" fn(value: c_int, user_data: *mut c_void);
extern "C" {
fn register_callback(callback: Callback, user_data: *mut c_void);
fn process_items(items: *const c_int, count: usize);
fn unregister_callback();
}
// Rust callback function with C ABI
extern "C" fn rust_callback(value: c_int, user_data: *mut c_void) {
unsafe {
// SAFETY: user_data must be a valid pointer to our context
if !user_data.is_null() {
let context = &mut *(user_data as *mut CallbackContext);
context.sum += value as i64;
context.count += 1;
println!("Callback received: {}", value);
}
}
}
struct CallbackContext {
sum: i64,
count: usize,
}
fn example_callbacks() {
let mut context = CallbackContext { sum: 0, count: 0 };
unsafe {
// SAFETY: context lives longer than the callback registration
let context_ptr = &mut context as *mut _ as *mut c_void;
register_callback(rust_callback, context_ptr);
// Process some items, which will trigger callbacks
let items = vec![10, 20, 30, 40, 50];
process_items(items.as_ptr(), items.len());
unregister_callback();
}
println!("Total sum: {}, count: {}", context.sum, context.count);
}
use std::marker::PhantomData;
use std::os::raw::c_void;
// Opaque type - we don't know its layout
#[repr(C)]
pub struct OpaqueHandle {
_private: [u8; 0],
_marker: PhantomData<(*mut u8, std::marker::PhantomPinned)>,
}
extern "C" {
fn create_handle(config: *const u8, len: usize) -> *mut OpaqueHandle;
fn use_handle(handle: *const OpaqueHandle) -> i32;
fn destroy_handle(handle: *mut OpaqueHandle);
}
/// Safe wrapper around opaque C handle
pub struct Handle {
ptr: *mut OpaqueHandle,
}
impl Handle {
pub fn new(config: &[u8]) -> Result<Self, &'static str> {
unsafe {
// SAFETY: config is a valid slice
let ptr = create_handle(config.as_ptr(), config.len());
if ptr.is_null() {
Err("Failed to create handle")
} else {
Ok(Handle { ptr })
}
}
}
pub fn use_it(&self) -> i32 {
unsafe {
// SAFETY: ptr is valid for the lifetime of self
use_handle(self.ptr)
}
}
}
impl Drop for Handle {
fn drop(&mut self) {
unsafe {
// SAFETY: ptr is valid and we own it
destroy_handle(self.ptr);
}
}
}
// Ensure Handle is not Send/Sync if the C library isn't thread-safe
// Uncomment if needed:
// impl !Send for Handle {}
// impl !Sync for Handle {}
fn example_opaque_types() {
let config = b"some configuration";
match Handle::new(config) {
Ok(handle) => {
let result = handle.use_it();
println!("Handle result: {}", result);
// handle is automatically destroyed here
}
Err(e) => eprintln!("Error: {}", e),
}
}
use std::os::raw::{c_int, c_char};
use std::ffi::CStr;
extern "C" {
fn perform_operation(input: c_int) -> c_int;
fn get_last_error() -> c_int;
fn get_error_string(error_code: c_int) -> *const c_char;
}
#[derive(Debug)]
pub enum FFIError {
InvalidInput,
OutOfMemory,
IoError,
Unknown(i32),
}
impl std::fmt::Display for FFIError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
FFIError::InvalidInput => write!(f, "Invalid input"),
FFIError::OutOfMemory => write!(f, "Out of memory"),
FFIError::IoError => write!(f, "I/O error"),
FFIError::Unknown(code) => write!(f, "Unknown error: {}", code),
}
}
}
impl std::error::Error for FFIError {}
impl FFIError {
fn from_error_code(code: i32) -> Self {
match code {
1 => FFIError::InvalidInput,
2 => FFIError::OutOfMemory,
3 => FFIError::IoError,
_ => FFIError::Unknown(code),
}
}
fn get_message(&self) -> Option<String> {
if let FFIError::Unknown(code) = self {
unsafe {
let ptr = get_error_string(*code);
if !ptr.is_null() {
let c_str = CStr::from_ptr(ptr);
return c_str.to_str().ok().map(|s| s.to_owned());
}
}
}
None
}
}
fn safe_perform_operation(input: i32) -> Result<i32, FFIError> {
unsafe {
// SAFETY: perform_operation is a valid C function
let result = perform_operation(input);
if result < 0 {
// Operation failed, get error code
let error_code = get_last_error();
Err(FFIError::from_error_code(error_code))
} else {
Ok(result)
}
}
}
fn example_error_handling() {
match safe_perform_operation(42) {
Ok(result) => println!("Success: {}", result),
Err(e) => {
eprintln!("Error: {}", e);
if let Some(msg) = e.get_message() {
eprintln!("Details: {}", msg);
}
}
}
}
use std::os::raw::{c_int, c_float};
use std::slice;
extern "C" {
fn sum_array(arr: *const c_float, len: usize) -> c_float;
fn modify_array(arr: *mut c_int, len: usize);
fn allocate_buffer(size: usize) -> *mut u8;
fn free_buffer(ptr: *mut u8);
fn fill_buffer(buffer: *mut u8, size: usize, pattern: u8);
}
/// Safe wrapper for summing an array
fn safe_sum_array(arr: &[f32]) -> f32 {
unsafe {
// SAFETY: arr is a valid slice
sum_array(arr.as_ptr(), arr.len())
}
}
/// Safe wrapper for modifying an array
fn safe_modify_array(arr: &mut [i32]) {
unsafe {
// SAFETY: arr is a valid mutable slice
modify_array(arr.as_mut_ptr(), arr.len());
}
}
/// RAII wrapper for C-allocated buffer
pub struct CBuffer {
ptr: *mut u8,
size: usize,
}
impl CBuffer {
pub fn new(size: usize) -> Option<Self> {
unsafe {
let ptr = allocate_buffer(size);
if ptr.is_null() {
None
} else {
Some(CBuffer { ptr, size })
}
}
}
pub fn fill(&mut self, pattern: u8) {
unsafe {
// SAFETY: ptr is valid and size is correct
fill_buffer(self.ptr, self.size, pattern);
}
}
pub fn as_slice(&self) -> &[u8] {
unsafe {
// SAFETY: ptr is valid for size bytes
slice::from_raw_parts(self.ptr, self.size)
}
}
pub fn as_mut_slice(&mut self) -> &mut [u8] {
unsafe {
// SAFETY: ptr is valid for size bytes and we have exclusive access
slice::from_raw_parts_mut(self.ptr, self.size)
}
}
}
impl Drop for CBuffer {
fn drop(&mut self) {
unsafe {
// SAFETY: ptr was allocated by allocate_buffer
free_buffer(self.ptr);
}
}
}
// Safety: If the C library is thread-safe
unsafe impl Send for CBuffer {}
unsafe impl Sync for CBuffer {}
fn example_array_handling() {
let arr = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let sum = safe_sum_array(&arr);
println!("Sum: {}", sum);
let mut arr2 = vec![1, 2, 3, 4, 5];
safe_modify_array(&mut arr2);
println!("Modified: {:?}", arr2);
if let Some(mut buffer) = CBuffer::new(1024) {
buffer.fill(0xFF);
println!("Buffer first bytes: {:?}", &buffer.as_slice()[..10]);
}
}
C and Rust have different type representations. Use #[repr(C)] to ensure Rust types match C's layout:
#[repr(C)]
struct Point {
x: f64,
y: f64,
}
FFI boundaries are inherently unsafe. Safe wrappers ensure:
Use extern "C" to specify C calling convention:
extern "C" fn callback(x: i32) -> i32 {
x * 2
}
C can return null pointers. Always check:
unsafe {
let ptr = c_function();
if ptr.is_null() {
return Err("Null pointer");
}
// Safe to use ptr now
}
extern "C" {
fn get_data() -> *const u8;
}
// ❌ DON'T: Dereference without null check
unsafe fn bad() -> u8 {
*get_data() // May be null!
}
// ✅ DO: Check for null first
unsafe fn good() -> Option<u8> {
let ptr = get_data();
if ptr.is_null() {
None
} else {
Some(*ptr)
}
}
extern "C" {
fn allocate_data() -> *mut u8;
fn free_data(ptr: *mut u8);
}
// ❌ DON'T: Forget to free
unsafe fn leak() {
let ptr = allocate_data();
// Use ptr
// Oops, never freed!
}
// ✅ DO: Use RAII wrapper
struct Data(*mut u8);
impl Drop for Data {
fn drop(&mut self) {
unsafe { free_data(self.0); }
}
}
use std::ffi::CString;
// ❌ DON'T: Ignore null bytes in strings
fn bad_string(s: &str) {
let c_str = CString::new(s).unwrap(); // Panics if s contains null!
}
// ✅ DO: Handle null bytes gracefully
fn good_string(s: &str) -> Result<CString, std::ffi::NulError> {
CString::new(s)
}
use std::ffi::CString;
extern "C" {
fn store_string(s: *const i8);
}
// ❌ DON'T: Temporary CString
unsafe fn bad() {
let s = CString::new("hello").unwrap();
store_string(s.as_ptr());
// s is dropped here, but C code may still reference it!
}
// ✅ DO: Ensure lifetime
unsafe fn good(s: &CString) {
store_string(s.as_ptr());
// Caller ensures s lives long enough
}
// ❌ DON'T: Assume C library is thread-safe
struct Handle(*mut std::ffi::c_void);
unsafe impl Send for Handle {} // Dangerous if C library isn't thread-safe!
unsafe impl Sync for Handle {}
// ✅ DO: Document and verify thread safety
struct SafeHandle(*mut std::ffi::c_void);
// Only implement if C library documentation guarantees thread safety
// unsafe impl Send for SafeHandle {}
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int, c_void};
use std::ptr;
// External C library declarations
extern "C" {
fn db_open(path: *const c_char) -> *mut c_void;
fn db_close(handle: *mut c_void);
fn db_get(handle: *mut c_void, key: *const c_char) -> *mut c_char;
fn db_set(handle: *mut c_void, key: *const c_char, value: *const c_char) -> c_int;
fn db_delete(handle: *mut c_void, key: *const c_char) -> c_int;
fn db_free_value(value: *mut c_char);
}
#[derive(Debug)]
pub enum DbError {
OpenFailed,
NotFound,
WriteFailed,
InvalidKey,
InvalidValue,
}
impl std::fmt::Display for DbError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
DbError::OpenFailed => write!(f, "Failed to open database"),
DbError::NotFound => write!(f, "Key not found"),
DbError::WriteFailed => write!(f, "Write operation failed"),
DbError::InvalidKey => write!(f, "Invalid key"),
DbError::InvalidValue => write!(f, "Invalid value"),
}
}
}
impl std::error::Error for DbError {}
/// Safe wrapper around C database library
pub struct Database {
handle: *mut c_void,
}
impl Database {
/// Open a database at the given path
pub fn open(path: &str) -> Result<Self, DbError> {
let c_path = CString::new(path).map_err(|_| DbError::InvalidKey)?;
unsafe {
// SAFETY: c_path is a valid null-terminated string
let handle = db_open(c_path.as_ptr());
if handle.is_null() {
Err(DbError::OpenFailed)
} else {
Ok(Database { handle })
}
}
}
/// Get a value by key
pub fn get(&self, key: &str) -> Result<String, DbError> {
let c_key = CString::new(key).map_err(|_| DbError::InvalidKey)?;
unsafe {
// SAFETY: handle is valid, c_key is a valid string
let value_ptr = db_get(self.handle, c_key.as_ptr());
if value_ptr.is_null() {
return Err(DbError::NotFound);
}
// Convert C string to Rust String
let c_str = CStr::from_ptr(value_ptr);
let result = c_str
.to_str()
.map_err(|_| DbError::InvalidValue)?
.to_owned();
// Free the C-allocated string
db_free_value(value_ptr);
Ok(result)
}
}
/// Set a key-value pair
pub fn set(&mut self, key: &str, value: &str) -> Result<(), DbError> {
let c_key = CString::new(key).map_err(|_| DbError::InvalidKey)?;
let c_value = CString::new(value).map_err(|_| DbError::InvalidValue)?;
unsafe {
// SAFETY: handle is valid, both strings are valid
let result = db_set(self.handle, c_key.as_ptr(), c_value.as_ptr());
if result == 0 {
Ok(())
} else {
Err(DbError::WriteFailed)
}
}
}
/// Delete a key
pub fn delete(&mut self, key: &str) -> Result<(), DbError> {
let c_key = CString::new(key).map_err(|_| DbError::InvalidKey)?;
unsafe {
// SAFETY: handle is valid, c_key is valid
let result = db_delete(self.handle, c_key.as_ptr());
if result == 0 {
Ok(())
} else {
Err(DbError::NotFound)
}
}
}
}
impl Drop for Database {
fn drop(&mut self) {
unsafe {
// SAFETY: handle is valid and we own it
db_close(self.handle);
}
}
}
// Only implement if the C library is thread-safe
// unsafe impl Send for Database {}
// unsafe impl Sync for Database {}
fn database_example() {
match Database::open("/tmp/test.db") {
Ok(mut db) => {
// Set some values
if let Err(e) = db.set("name", "Alice") {
eprintln!("Error setting value: {}", e);
}
// Get a value
match db.get("name") {
Ok(value) => println!("Name: {}", value),
Err(e) => eprintln!("Error getting value: {}", e),
}
// Delete a key
if let Err(e) = db.delete("name") {
eprintln!("Error deleting key: {}", e);
}
}
Err(e) => eprintln!("Error opening database: {}", e),
}
}
use std::collections::HashMap;
use std::os::raw::{c_int, c_void};
use std::sync::{Arc, Mutex};
type NativeCallback = extern "C" fn(event_type: c_int, data: *const c_void, user_data: *mut c_void);
extern "C" {
fn register_event_handler(callback: NativeCallback, user_data: *mut c_void) -> c_int;
fn unregister_event_handler(handler_id: c_int);
fn trigger_event(event_type: c_int, data: *const c_void);
}
/// Rust-friendly event handler trait
pub trait EventHandler: Send {
fn handle_event(&mut self, event_type: i32, data: &[u8]);
}
/// Safe wrapper for event system
pub struct EventSystem {
handlers: Arc<Mutex<HashMap<i32, Box<dyn EventHandler>>>>,
handler_id: Option<i32>,
}
impl EventSystem {
pub fn new() -> Self {
EventSystem {
handlers: Arc::new(Mutex::new(HashMap::new())),
handler_id: None,
}
}
pub fn register_handler<H: EventHandler + 'static>(&mut self, event_type: i32, handler: H) {
let mut handlers = self.handlers.lock().unwrap();
handlers.insert(event_type, Box::new(handler));
}
pub fn start(&mut self) -> Result<(), &'static str> {
// Create a boxed context to pass to C
let context = Box::new(Arc::clone(&self.handlers));
let context_ptr = Box::into_raw(context) as *mut c_void;
unsafe {
// SAFETY: context_ptr is a valid pointer to our Arc
let id = register_event_handler(event_callback, context_ptr);
if id < 0 {
// Failed to register, reclaim the box
let _ = Box::from_raw(context_ptr as *mut Arc<Mutex<HashMap<i32, Box<dyn EventHandler>>>>);
Err("Failed to register handler")
} else {
self.handler_id = Some(id);
Ok(())
}
}
}
pub fn stop(&mut self) {
if let Some(id) = self.handler_id.take() {
unsafe {
unregister_event_handler(id);
}
}
}
}
impl Drop for EventSystem {
fn drop(&mut self) {
self.stop();
}
}
extern "C" fn event_callback(event_type: c_int, data: *const c_void, user_data: *mut c_void) {
unsafe {
// SAFETY: user_data is a valid pointer to our Arc
if user_data.is_null() {
return;
}
let handlers_ptr = user_data as *const Arc<Mutex<HashMap<i32, Box<dyn EventHandler>>>>;
let handlers_arc = &*handlers_ptr;
let mut handlers = handlers_arc.lock().unwrap();
if let Some(handler) = handlers.get_mut(&event_type) {
// Convert data to slice (assuming it's a simple byte buffer)
// In real code, you'd have a more sophisticated protocol
let data_slice = if !data.is_null() {
std::slice::from_raw_parts(data as *const u8, 64) // Example size
} else {
&[]
};
handler.handle_event(event_type, data_slice);
}
}
}
// Example event handler implementation
struct LoggingHandler;
impl EventHandler for LoggingHandler {
fn handle_event(&mut self, event_type: i32, data: &[u8]) {
println!("Event {}: {} bytes", event_type, data.len());
}
}
fn event_system_example() {
let mut system = EventSystem::new();
system.register_handler(1, LoggingHandler);
if let Err(e) = system.start() {
eprintln!("Error starting event system: {}", e);
return;
}
// Event system is now running
// Events from C will be handled by our Rust handlers
system.stop();
}
use std::os::raw::c_void;
use std::slice;
extern "C" {
fn create_buffer(size: usize) -> *mut c_void;
fn destroy_buffer(buffer: *mut c_void);
fn get_buffer_data(buffer: *mut c_void) -> *mut u8;
fn get_buffer_size(buffer: *mut c_void) -> usize;
fn process_buffer(buffer: *mut c_void) -> i32;
}
/// Zero-copy wrapper around C buffer
pub struct SharedBuffer {
handle: *mut c_void,
}
impl SharedBuffer {
pub fn new(size: usize) -> Option<Self> {
unsafe {
let handle = create_buffer(size);
if handle.is_null() {
None
} else {
Some(SharedBuffer { handle })
}
}
}
/// Get a slice view of the buffer without copying
pub fn as_slice(&self) -> &[u8] {
unsafe {
let ptr = get_buffer_data(self.handle);
let size = get_buffer_size(self.handle);
slice::from_raw_parts(ptr, size)
}
}
/// Get a mutable slice view
pub fn as_mut_slice(&mut self) -> &mut [u8] {
unsafe {
let ptr = get_buffer_data(self.handle);
let size = get_buffer_size(self.handle);
slice::from_raw_parts_mut(ptr, size)
}
}
/// Process buffer in-place using C code
pub fn process(&mut self) -> Result<(), &'static str> {
unsafe {
let result = process_buffer(self.handle);
if result == 0 {
Ok(())
} else {
Err("Processing failed")
}
}
}
}
impl Drop for SharedBuffer {
fn drop(&mut self) {
unsafe {
destroy_buffer(self.handle);
}
}
}
fn zero_copy_example() {
if let Some(mut buffer) = SharedBuffer::new(1024) {
// Write to buffer
let slice = buffer.as_mut_slice();
for (i, byte) in slice.iter_mut().enumerate() {
*byte = (i % 256) as u8;
}
// Process in-place
if let Err(e) = buffer.process() {
eprintln!("Error: {}", e);
}
// Read results
let result = buffer.as_slice();
println!("First 10 bytes: {:?}", &result[..10]);
}
}
// libc bindings
use libc::{c_int, c_void, size_t};
extern "C" {
fn socket(domain: c_int, ty: c_int, protocol: c_int) -> c_int;
fn bind(sockfd: c_int, addr: *const c_void, addrlen: size_t) -> c_int;
}
// openssl-sys crate provides FFI bindings
use openssl_sys::*;
// High-level wrapper in openssl crate
use openssl::ssl::{SslConnector, SslMethod};
// rusqlite wraps sqlite3 C library
use rusqlite::{Connection, Result};
let conn = Connection::open("my_db.db")?;
conn.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)", [])?;
// git2 crate wraps libgit2
use git2::Repository;
let repo = Repository::open(".")?;
let head = repo.head()?;
// sdl2 crate wraps SDL2 C library
use sdl2::pixels::Color;
use sdl2::event::Event;
let sdl_context = sdl2::init()?;
let video_subsystem = sdl_context.video()?;
fn main() {
println!("=== Basic FFI ===");
example_basic_ffi();
println!("\n=== String Handling ===");
example_string_handling();
println!("\n=== Struct Passing ===");
example_struct_passing();
println!("\n=== Callbacks ===");
example_callbacks();
println!("\n=== Opaque Types ===");
example_opaque_types();
println!("\n=== Error Handling ===");
example_error_handling();
println!("\n=== Array Handling ===");
example_array_handling();
println!("\n=== Database Example ===");
database_example();
println!("\n=== Event System ===");
event_system_example();
println!("\n=== Zero-Copy ===");
zero_copy_example();
}
Run this code in the official Rust Playground