use std::ffi::{CStr, CString}; use std::os::raw::c_char; // For this snippet to run directly, you would need to: // 1. Create a C source file (e.g., `src/lib_example.c`): // ```c // #include // #include // // A simple C function to concatenate two strings into a buffer. // // Caller's responsibility to ensure 'dest' is large enough. // // Returns the length of the new string (excluding null), or -1 if buffer is too small or invalid args. // int concatenate_strings(char* dest, const char* str1, const char* str2, size_t dest_size) { // if (!dest || !str1 || !str2 || dest_size == 0) { // return -1; // Invalid arguments // } // size_t len1 = strlen(str1); // size_t len2 = strlen(str2); // size_t required_len = len1 + len2 + 1; // +1 for null terminator // if (dest_size < required_len) { // return -1; // Buffer too small // } // strcpy(dest, str1); // strcat(dest, str2); // return (int)(required_len - 1); // Return actual string length // } // ``` // 2. Create a C header file (e.g., `src/lib_example.h`): // ```c // #ifndef LIB_EXAMPLE_H // #define LIB_EXAMPLE_H // #include // For size_t // int concatenate_strings(char* dest, const char* str1, const char* str2, size_t dest_size); // #endif // LIB_EXAMPLE_H // ``` // 3. Create a `build.rs` file in your project root to compile the C code: // ```rust // fn main() { // cc::Build::new() // .file("src/lib_example.c") // Path to your C source file // .compile("lib_example"); // Output library name // println!("cargo:rustc-link-lib=static=lib_example"); // Link against static library // } // ``` // 4. Add `libc = "0.2"` to your Cargo.toml and `cc = "1.0"` to your Cargo.toml `[build-dependencies]` section. // Define the foreign function interface. // This block is inherently `unsafe` because Rust cannot verify the C function's correctness. #[link(name = "lib_example", kind = "static")] // Link to the compiled C library extern "C" { fn concatenate_strings( dest: *mut c_char, str1: *const c_char, str2: *const c_char, dest_size: libc::size_t, // Using `libc::size_t` for C's size_t type ) -> libc::c_int; } /// Safely concatenates two Rust strings using a C FFI function. /// /// This function encapsulates the `unsafe` FFI call, providing a safe /// Rust interface and handling potential errors from the C function. /// It ensures that the buffer for the C function is correctly sized and /// that CStrings are properly managed, preventing memory corruption. fn safe_ffi_concatenate(s1: &str, s2: &str) -> Result { // Convert Rust strings to null-terminated CStrings. // `CString::new` checks for internal null bytes, preventing premature termination in C. let c_str1 = CString::new(s1).map_err(|e| format!("Invalid string 1 (contains null byte): {}", e))?; let c_str2 = CString::new(s2).map_err(|e| format!("Invalid string 2 (contains null byte): {}", e))?; // Calculate required buffer size for C function (includes null terminator). let max_len = s1.len() + s2.len(); let buffer_size = max_len + 1; // Allocate a buffer. `Vec` guarantees contiguous memory and can be safely // cast to a mutable raw pointer. `with_capacity` reserves space but doesn't set length. let mut buffer = Vec::::with_capacity(buffer_size); // The `unsafe` block explicitly marks code that requires careful reasoning about memory safety. // All interactions with FFI are inherently `unsafe`. let result_code = unsafe { // SAFETY CRITERIA FOR THIS `unsafe` BLOCK: // 1. `buffer.as_mut_ptr()` provides a valid mutable pointer to `buffer_size` bytes // of allocated, owned memory. We cast `*mut u8` to `*mut c_char` which is valid // as `c_char` is typically `i8` or `u8`. // 2. `c_str1.as_ptr()` and `c_str2.as_ptr()` provide valid const pointers to // null-terminated C strings. Their lifetimes are managed by `CString`. // 3. `buffer_size` is passed, ensuring the C function knows the maximum writable length. // 4. The C function `concatenate_strings` is assumed to correctly write a null-terminated // string into `dest` without exceeding `dest_size`. // 5. The return value from the C function is checked for error conditions. concatenate_strings( buffer.as_mut_ptr() as *mut c_char, // Cast `*mut u8` to `*mut c_char` c_str1.as_ptr(), c_str2.as_ptr(), buffer_size as libc::size_t, // Cast `usize` to `libc::size_t` ) }; if result_code >= 0 { // SAFETY CRITERIA FOR THIS `unsafe` BLOCK: // 1. The C function guarantees that `result_code` bytes (excluding null terminator) // have been written and that the buffer is null-terminated at this length. // 2. We can safely set the vector's length to `result_code` because valid data // has been written up to that point. // 3. `CStr::from_ptr` creates a safe CStr slice from the buffer's contents, // which is then converted to a Rust `String`. unsafe { buffer.set_len(result_code as usize); // Set the vector's length to the actual string length let c_string = CStr::from_ptr(buffer.as_ptr() as *const c_char); Ok(c_string.to_string_lossy().into_owned()) // Convert CStr to owned Rust String } } else { Err(format!("C function 'concatenate_strings' failed with code: {}", result_code)) } } fn main() { println!("--- Demonstrating Safe FFI --- "); match safe_ffi_concatenate("Hello, ", "Rust!") { Ok(s) => println!("FFI Concatenated (success): '{}'", s), Err(e) => println!("FFI Concatenated (error): {}", e), } // Simulate a case where the C function might detect buffer overflow match safe_ffi_concatenate( "ThisIsAVeryLongStringPartOne", "AndThisIsAnotherVeryLongStringPartTwoTryingToOverflowSmallBuffer" ) { Ok(s) => println!("FFI Concatenated (success): '{}'", s), Err(e) => println!("FFI Concatenated (error): {}", e), } // Example of a Rust string with an internal null byte, which CString prevents. match safe_ffi_concatenate("Hello\0World", "!") { Ok(s) => println!("FFI Concatenated (success): '{}'", s), Err(e) => println!("FFI Concatenated (error): {}", e), // This will trigger the CString error } }