### Install GUI Executable Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/gui/CMakeLists.txt Installs the built 'gui' executable to the 'bin' directory on the target system. This makes the application available after installation. ```cmake install(TARGETS gui DESTINATION bin) ``` -------------------------------- ### Install Executable Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/fun/problem_2/CMakeLists.txt Installs the 'problem_2' executable into the 'bin' directory of the installation prefix. ```cmake install(TARGETS problem_2 DESTINATION bin) ``` -------------------------------- ### Clone Rust FFI Guide Repository Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/README.md Use this command to download the source code for the guide. ```bash $ git clone https://github.com/Michael-F-Bryan/rust-ffi-guide ``` -------------------------------- ### Serve Rust FFI Guide Locally Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/README.md Build and serve the book locally using mdbook. The book will be available at http://localhost:3000/. ```bash $ mdbook serve --open ``` -------------------------------- ### GUI Main Application (C++) Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/setting_up.md A basic Qt application that displays a 'Hello World' button. Requires Qt libraries to be installed. ```cpp // gui/main.cpp #include #include int main(int argc, char **argv) { QApplication app(argc, argv); QPushButton button("Hello World"); button.show(); app.exec(); } ``` -------------------------------- ### Install Executable Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/fun/problem_3/CMakeLists.txt Installs the built C++ executable to the 'bin' directory. ```cmake install(TARGETS problem_3 DESTINATION bin) ``` -------------------------------- ### Install mdbook Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/README.md Install the mdbook tool, which is used for building and serving the documentation. ```bash $ cargo install mdbook ``` -------------------------------- ### Install Executable Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/fun/problem_4/CMakeLists.txt Installs the 'problem_4' executable target into the 'bin' directory. ```cmake install(TARGETS problem_4 DESTINATION bin) ``` -------------------------------- ### Install cbindgen Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/cbindgen.md Install the cbindgen command-line tool globally using cargo. ```bash $ cargo install cbindgen $ cd /path/to/my/project && cbindgen . -o target/my_project.h ``` -------------------------------- ### Rust Dynamic Library Loading Example Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Demonstrates loading a dynamic library and calling an exported function using the `libloading` crate. It expects the library path as a command-line argument. ```rust extern crate libloading; use std::env; use libloading::{Library, Symbol}; type AddFunc = fn(isize, isize) -> isize; fn main() { let library_path = env::args().nth(1).expect("USAGE: loading "); println!("Loading add() from {}", library_path); let lib = Library::new(library_path).unwrap(); unsafe { let func: Symbol = lib.get(b"add").unwrap(); let answer = func(1, 2); println!("1 + 2 = {}", answer); } } ``` -------------------------------- ### Running the Dynamic Library Loader Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Command to compile and run the Rust dynamic library loading example, passing the path to the compiled dynamic library as an argument. ```bash cargo run -- ../libadder.so ``` -------------------------------- ### Compile and Test with Docker Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/README.md Use a Docker container to compile and test the application if local dependencies are not installed. This command mounts the current directory and configures Cargo home. ```bash $ docker run \ -v $(pwd):/code \ -v ~/.cargo:$HOME/.cargo \ -e CARGO_HOME=$HOME/.cargo \ --user $UID \ michaelfbryan/ffi-guide ci/test.sh ``` -------------------------------- ### Generated C++ Header File Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/cbindgen.md Example of a C++ header file generated by cbindgen, including forward declarations and function signatures with a specified namespace. ```cpp #include #include extern "C" { namespace ffi { // A HTTP request. struct Request; struct Response; // Initialize the global logger and log to `rest_client.log`. // // Note that this is an idempotent function, so you can call it as many // times as you want and logging will only be initialized the first time. void initialize_logging(); // Construct a new `Request` which will target the provided URL and fill out // all other fields with their defaults. // // # Note ... ``` -------------------------------- ### Set Minimum CMake Version and Project Name Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/CMakeLists.txt Specifies the minimum required CMake version and defines the project name. This is a standard starting point for any CMake project. ```cmake cmake_minimum_required(VERSION 3.7) project(rest-client) ``` -------------------------------- ### Rust Function to Get Home Directory Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/fun/problems.md A Rust library function 'home_directory' that returns a C-style string pointer to the user's home directory. ```rust // home.rs use std::ffi::CString; use std::env; use std::ptr; use std::os::c_char; #[no_mangle] pub extern "C" fn home_directory() -> *const c_char { let home = match env::home_dir() { Some(p) => p, None => return ptr::null(), }; let c_string = match CString::new(home){ Ok(s) => s, Err(_) => return ptr::null(), }; c_string.as_ptr() } ``` -------------------------------- ### Main Application Entry Point Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/setting_up.md Set up the QApplication and initialize the MainWindow to run the GUI application. ```cpp // gui/main.cpp #include "main_window.hpp" #include int main(int argc, char **argv) { QApplication app(argc, argv); MainWindow mainWindow; mainWindow.show(); app.exec(); } ``` -------------------------------- ### Build and Run Project Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/setting_up.md Commands to create a build directory, configure the project with CMake, compile, and run the executable. ```bash $ mkdir build && cd build $ cmake .. $ make $ ./gui/gui ``` -------------------------------- ### Build Project Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Performs a build of the entire project, including the new plugin, as a sanity check. ```bash $ mkdir build && cd build $ cmake -DCMAKE_BUILD_TYPE=Debug .. $ make ... ``` -------------------------------- ### GUI - MainWindow onClick Method Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/send_basic.md Demonstrates how to use the C++ Request and Response wrappers within a GUI application. It creates a request, sends it, reads the response body, and prints it to the console. ```cpp // gui/main_window.cpp void MainWindow::onClick() { std::cout << "Creating the request" << std::endl; Request req("https://www.rust-lang.org/"); std::cout << "Sending Request" << std::endl; Response res = req.send(); std::cout << "Received Response" << std::endl; std::vector raw_body = res.read_body(); std::string body(raw_body.begin(), raw_body.end()); std::cout << body << std::endl; } ``` -------------------------------- ### Implement MainWindow Methods and FFI Call Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/setting_up.md Implement the MainWindow constructor and the onClick slot. The onClick slot calls the external Rust function `hello_world`. ```cpp // gui/main_window.cpp #include "main_window.hpp" extern "C" { void hello_world(); } void MainWindow::onClick() { // Call the `hello_world` function to print a message to stdout hello_world(); } MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { button = new QPushButton("Click Me", this); // Connect the button's `released` signal to `this->onClick()` connect(button, SIGNAL(released()), this, SLOT(onClick())); } ``` -------------------------------- ### Create new header and source files Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/setting_up.md Create new header and source files for the main window component to modularize the GUI code. ```bash $ touch gui/main_window.hpp gui/main_window.cpp ``` -------------------------------- ### Conditional C++ Executable Compilation Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/fun/problem_1/CMakeLists.txt Conditionally compiles a C++ executable named 'problem_1' if the 'WITH_PROBLEM_1' CMake option is enabled. It links the Rust library and sets up dependencies and installation. ```cmake option(WITH_PROBLEM_1 "Try to compile problem 1" OFF) if(WITH_PROBLEM_1) add_executable(problem_1 ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp) target_link_libraries(problem_1 ${CMAKE_CURRENT_BINARY_DIR}/libadder.so) add_dependencies(problem_1 problem_1_lib) install(TARGETS problem_1 DESTINATION bin) endif(WITH_PROBLEM_1) ``` -------------------------------- ### Run GUI and Load Plugin Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Execute the GUI application with specific environment variables to enable debug logging for client and plugin, then load the plugin from its shared object file. ```bash $ RUST_LOG=client=debug,injector_plugin=debug ./gui/gui ``` -------------------------------- ### Get Last Error Message Length (C API) Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/errors/return_types.md A C-callable function that returns the length of the last error message in bytes, not including null characters. Returns 0 if no error is set. ```rust // client/src/ffi.rs /// Calculate the number of bytes in the last error's error message **not** /// including any trailing `null` characters. #[no_mangle] pub extern "C" fn last_error_length() -> c_int { LAST_ERROR.with(|prev| match *prev.borrow() { Some(ref err) => err.to_string().len() as c_int + 1, None => 0, }) } ``` -------------------------------- ### Create Project Directories Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/setting_up.md Initializes the project structure by creating necessary directories and files for a CMake project. ```bash $ mkdir rest_client && cd rest_client $ mkdir gui $ touch gui/main.cpp $ touch CMakeLists.txt ``` -------------------------------- ### Rust FFI: Get Response Body Length Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/send_basic.md Retrieves the length of the response body. Returns 0 if the response pointer is null. This is used to determine the size of the buffer needed for copying the body. ```rust // client/src/ffi.rs use libc::{c_char, size_t}; ... /// Get the length of a `Response`'s body. #[no_mangle] pub unsafe extern "C" fn response_body_length(res: *const Response) -> size_t { if res.is_null() { return 0; } (&*res).body.len() as size_t } ``` -------------------------------- ### Create New Library Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Creates a new Rust library project for the plugin. ```bash $ cargo new injector-plugin ``` -------------------------------- ### Compile the Plugin Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Compile the plugin using the make command in the build directory. ```bash $ cd build $ make ``` -------------------------------- ### Define Client Build Directory and Include Path Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/gui/CMakeLists.txt Sets a variable for the client's build directory and adds it to the include directories. This is used for linking against a separately built client library. ```cmake set(CLIENT_BUILD_DIR ${CMAKE_BINARY_DIR}/client) include_directories(${CLIENT_BUILD_DIR}) ``` -------------------------------- ### Initialize Logging with Fern Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/client.md Sets up logging for the client library using the Fern crate. It configures the log format, level, and output to a file named 'rest_client.log'. This function is designed to be called only once using std::sync::Once to prevent panics. ```rust fn initialize_logging() { static INIT: std::sync::Once = std::sync::Once::new(); INIT.call_once(|| { fern::Dispatch::new() .format(|out, message, record| { let thread = std::thread::current(); let thread_name = thread.name().unwrap_or(""); let location = record.file_static().unwrap_or("unknown"); let line = record.line().unwrap_or(0); out.finish(format_args!( "{} [{}] {} ({}#{}): {}", Local::now().format("[%Y-%m-%d][%H:%M:%S]"), thread_name, record.level(), location, line, message )) }) .level(LogLevelFilter::Debug) .chain(fern::log_file("rest_client.log").unwrap()) .apply() .unwrap(); }); } ``` -------------------------------- ### Copy CMakeLists.txt Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Copies the CMake build configuration from the client library to the new plugin directory. ```bash $ cp ./client/CMakeLists.txt ./injector-plugin/CMakeLists.txt ``` -------------------------------- ### Configure Cargo.toml for build script Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/cbindgen.md Add a build script entry and cbindgen build dependency to Cargo.toml. ```diff ... description = "The business logic for a REST client" name = "client" repository = "https://github.com/Michael-F-Bryan/rust-ffi-guide" version = "0.1.0" + build = "build.rs" + + [build-dependencies] + cbindgen = "0.1.29" [dependencies] chrono = "0.4.0" ... ``` -------------------------------- ### Root CMakeLists.txt Configuration Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/setting_up.md Defines the main project settings, including minimum CMake version, project name, and adds subdirectories for different modules. ```cmake # CMakeLists.txt cmake_minimum_required(VERSION 3.7) project(rest-client) enable_testing() add_subdirectory(client) add_subdirectory(gui) ``` -------------------------------- ### GUI CMakeLists.txt Configuration Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/basic_request.md Configures the CMake build system for the GUI directory, including setting C++ standard, enabling Qt features, and linking necessary libraries and targets. ```cmake # gui/CMakeLists.txt set(CMAKE_CXX_STANDARD 14) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) find_package(Qt5Widgets) set(SOURCE main_window.cpp main_window.hpp wrappers.cpp wrappers.hpp main.cpp) add_executable(gui ${SOURCE}) get_target_property(CLIENT_DIR client LOCATION) target_link_libraries(gui Qt5::Widgets) target_link_libraries(gui ${CLIENT_DIR}/libclient.so) add_dependencies(gui client) ``` -------------------------------- ### Verify Plugin Symbol Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Uses the `nm` tool to inspect the compiled shared library and verify the presence of the `_plugin_create` symbol, indicating successful plugin registration. ```bash $ cd build $ make $ nm injector-plugin/libinjector_plugin.so | grep ' T ' ... 0000000000030820 T _plugin_create ... ``` -------------------------------- ### Initialize logging with fern and log Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/client.md Implement an idempotent function `initialize_logging` to set up global logging to a file named `rest_client.log` using `fern` and `log` crates. This is useful for debugging FFI bindings in GUI applications. ```rust // client/src/utils.rs use std::sync::{Once, ONCE_INIT}; use fern; use log::LogLevelFilter; use chrono::Local; use errors::*; /// Initialize the global logger and log to `rest_client.log`. /// /// Note that this is an idempotent function, so you can call it as many /// times as you want and logging will only be initialized the first time. #[no_mangle] pub extern "C" fn initialize_logging() { static INITIALIZE: Once = ONCE_INIT; INITIALIZE.call_once(|| { fern::Dispatch::new() .format(|out, message, record| { let loc = record.location(); out.finish(format_args!( ``` -------------------------------- ### GUI CMakeLists.txt Configuration Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/cbindgen.md Configures the CMake build for the GUI application, including setting include directories for client-side generated headers and defining the executable source files. ```cmake set(CMAKE_INCLUDE_CURRENT_DIR ON) find_package(Qt5Widgets) set(CLIENT_BUILD_DIR ${CMAKE_BINARY_DIR}/client) include_directories(${CLIENT_BUILD_DIR}) set(SOURCE main_window.cpp main_window.hpp wrappers.cpp wrappers.hpp main.cpp) add_executable(gui ${SOURCE}) ``` -------------------------------- ### Link GUI Executable with Libraries Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/gui/CMakeLists.txt Links the 'gui' executable against the Qt5 Widgets library and a custom client shared object ('CLIENT_SO'). It also ensures the 'client' target is built before 'gui'. ```cmake get_target_property(CLIENT_SO client LOCATION) target_link_libraries(gui Qt5::Widgets) target_link_libraries(gui ${CLIENT_SO}) add_dependencies(gui client) ``` -------------------------------- ### Add GUI Executable Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/gui/CMakeLists.txt Creates the GUI executable target named 'gui' using the specified source files. ```cmake add_executable(gui ${SOURCE}) ``` -------------------------------- ### Define Source Files Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/gui/CMakeLists.txt Lists all source files required for the GUI executable. This includes C++ source and header files. ```cmake set(SOURCE main_window.cpp main_window.hpp wrappers.cpp wrappers.hpp main.cpp) ``` -------------------------------- ### Initialize Plugin Manager Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Defines the `PluginManager` struct to hold loaded plugins and libraries. The `new` function initializes an empty manager. ```rust // client/src/plugins.rs pub struct PluginManager { plugins: Vec>, loaded_libraries: Vec, } impl PluginManager { pub fn new() -> PluginManager { PluginManager { plugins: Vec::new(), loaded_libraries: Vec::new(), } } ``` -------------------------------- ### Compiling Rust to a Dynamic Library Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Command to compile a Rust source file (`adder.rs`) into a dynamic library (`cdylib`). This is essential for runtime loading. ```bash rustc --crate-type cdylib adder.rs ``` -------------------------------- ### Enable Testing and Add Subdirectories Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/CMakeLists.txt Enables CMake's testing support and includes other subdirectories for building. This is typically placed at the end of the main CMakeLists.txt file. ```cmake enable_testing() add_subdirectory(client) add_subdirectory(injector-plugin) add_subdirectory(gui) add_subdirectory(book/fun) ``` -------------------------------- ### Define MainWindow Class Header Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/setting_up.md Declare the MainWindow class, inheriting from QMainWindow, with a QPushButton and a click handler slot. ```cpp // gui/main_window.hpp #include #include class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); private slots: void onClick(); private: QPushButton *button; }; ``` -------------------------------- ### Create Rust Library Crate Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/setting_up.md Initializes a new Rust library project using Cargo, intended to be compiled as a dynamic library. ```bash $ cargo new --lib client ``` -------------------------------- ### Load Dynamic Plugin Library Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Dynamically loads a plugin from a shared library file. It uses `libloading` to open the library, find the `_plugin_create` symbol, construct the plugin, and register it. The library must remain loaded for the plugin's lifetime. ```rust // client/src/plugins.rs pub unsafe fn load_plugin>(&mut self, filename: P) -> Result<()> { type PluginCreate = unsafe fn() -> *mut Plugin; let lib = Library::new(filename.as_ref()).chain_err(|| "Unable to load the plugin")?; // We need to keep the library around otherwise our plugin's vtable will // point to garbage. We do this little dance to make sure the library // doesn't end up getting moved. self.loaded_libraries.push(lib); let lib = self.loaded_libraries.last().unwrap(); let constructor: Symbol = lib.get(b"_plugin_create") .chain_err(|| "The `_plugin_create` symbol wasn't found.")?; let boxed_raw = constructor(); let plugin = Box::from_raw(boxed_raw); debug!("Loaded plugin: {}", plugin.name()); plugin.on_plugin_load(); self.plugins.push(plugin); Ok(()) } ``` -------------------------------- ### Configure Client Cargo.toml for rlib Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Ensures the client library also generates an rlib, allowing it to be linked by the plugin. ```toml # client/Cargo.toml ... [lib] crate-type = ["cdylib", "rlib"] ``` -------------------------------- ### Create a constructor for the Request struct Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/client.md Implement a `new` constructor for the `Request` struct to initialize a new HTTP request with a destination URL and method. ```rust impl Request { pub fn new(destination: Url, method: Method) -> Request { let headers = Headers::default(); let cookies = CookieJar::default(); let body = None; Request { destination, method, headers, cookies, body, } } } ``` -------------------------------- ### Run Test Script Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/README.md Execute the test script to compile and test the application using CMake. ```bash $ ./ci/test.sh ``` -------------------------------- ### Inspecting Dynamic Library Symbols Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Uses the `nm` tool to list symbols exported by a dynamic library (`libadder.so`), specifically filtering for the 'add' symbol to verify its export. ```bash nm libadder.so | grep 'add' ``` -------------------------------- ### Expected Debug Output and Response Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md This output shows the detailed logs from the client and the injector plugin during the request and response cycle, including the injected header and the final JSON response from httpbin.org. ```text DEBUG:client::ffi: Loading plugin, "/home/michael/Documents/ffi-guide/build/injector-plugin/libinjector_plugin.so" DEBUG:client::plugins: Loaded plugin: Header Injector INFO:injector_plugin: Injector loaded Creating the request Sending Request DEBUG:client::plugins: Firing pre_send hooks DEBUG:injector_plugin: Injected header into Request, Request { destination: "http://httpbin.org/get", method: Get, headers: {"some-dodgy-header": "true"}, cookies: CookieJar { original_cookies: {}, delta_cookies: {} }, body: None } INFO:client: Sending a GET request to http://httpbin.org/get DEBUG:client: Sending 1 Headers DEBUG:client: some-dodgy-header: true DEBUG:client::ffi: Received Response DEBUG:client::plugins: Firing post_receive hooks DEBUG:injector_plugin: Received Response DEBUG:injector_plugin: Headers: {"Connection": "keep-alive", "Server": "meinheld/0.6.1", "Date": "Tue, 07 Nov 2017 14:29:39 GMT", "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Credentials": "true", "X-Powered-By": "Flask", "X-Processed-Time": "0.000864028930664", "Content-Length": "303", "Via": "1.1 vegur"} Received Response Body: { "args": {}, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip", "Connection": "close", "Cookie": "", "Host": "httpbin.org", "Some-Dodgy-Header": "true", "User-Agent": "reqwest/0.8.0" }, "origin": "122.151.115.164", "url": "http://httpbin.org/get" } DEBUG:client::plugins: Unloading plugins INFO:injector_plugin: Injector unloaded ``` -------------------------------- ### Register Client Modules in Lib.rs Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/client.md Declares the four new modules (errors, utils, request, response) within the client's lib.rs file, making them available for use in the crate. ```rust pub mod errors; pub mod utils; mod request; mod response; ``` -------------------------------- ### Rust FFI Bindings for Request Lifecycle Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/basic_request.md Provides the Rust implementation for `request_create` and `request_destroy` functions. These are exposed via `extern "C"` and called by the C++ wrappers. ```rust #[no_mangle] pub unsafe extern "C" fn request_create(url: *const c_char) -> *mut Request { ... println!("Request created in Rust: {}", url_as_str); Box::into_raw(Box::new(req)) } ... #[no_mangle] pub unsafe extern "C" fn request_destroy(req: *mut Request) { if !req.is_null() { println!("Request was destroyed"); drop(Box::from_raw(req)); } } ``` -------------------------------- ### Rust Adder Library Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/fun/problems.md A simple Rust library exporting a C-compatible function 'add' for summing two unsigned 32-bit integers. ```rust // adder.rs pub extern "C" fn add(a: u32, b: u32) -> u32 { a + b } ``` -------------------------------- ### Catching and Displaying Exceptions in C++ Qt Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/errors/return_types.md This code block shows how to wrap a potentially error-throwing operation in a try-catch block to gracefully handle `WrapperException` and display an error message to the user via a Qt QMessageBox. ```cpp // gui/main_window.cpp void MainWindow::onClick() { try { Request req("this is an invalid URL"); } catch (const WrapperException& e) { QMessageBox::warning(this, "Error", e.what()); } } ``` -------------------------------- ### Rust FFI: PluginManager Creation and Destruction Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Exposes Rust's PluginManager to C++ for creation and destruction. Use `plugin_manager_new` to create an instance and `plugin_manager_destroy` to safely deallocate it. ```rust // client/src/ffi.rs use PluginManager; ... /// Create a new `PluginManager`. #[no_mangle] pub extern "C" fn plugin_manager_new() -> *mut PluginManager { Box::into_raw(Box::new(PluginManager::new())) } /// Destroy a `PluginManager` once you are done with it. #[no_mangle] pub unsafe extern "C" fn plugin_manager_destroy(pm: *mut PluginManager) { if !pm.is_null() { let pm = Box::from_raw(pm); drop(pm); } } ``` -------------------------------- ### Update Plugin Cargo.toml Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Configures the plugin's Cargo.toml to include dependencies, add a description, and specify cdylib and rlib crate types for dynamic loading and static linking. ```diff // injector-plugin/Cargo.toml [package] name = "injector-plugin" version = "0.1.0" authors = ["Michael Bryan "] + description = "A plugin which will stealthily inject a special header into your requests." [dependencies] + log = "0.3.8" + client = { path = "../client"} + + [lib] + crate-type = ["cdylib", "rlib"] ``` -------------------------------- ### Configure Rust Crate Type for Dynamic Library Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/setting_up.md Modifies the Cargo.toml file to specify that the Rust crate should be built as a C dynamic library (cdylib), essential for FFI. ```toml # client/Cargo.toml [package] name = "client" version = "0.1.0" authors = ["Michael Bryan "] description = "The business logic for a REST client" repository = "https://github.com/Michael-F-Bryan/rust-ffi-guide" [dependencies] [lib] crate-type = ["cdylib"] ``` -------------------------------- ### Updating GUI Click Handler Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/basic_request.md Modifies the `MainWindow::onClick` method to create a C++ `Request` object. This demonstrates the usage of the C++ wrapper and triggers FFI calls. ```cpp // gui/main_window.cpp #include "main_window.hpp" #include "wrappers.hpp" #include void MainWindow::onClick() { std::cout << "Creating the request" << std::endl; Request req("https://google.com/"); std::cout << "Request created in C++" << std::endl; } ... ``` -------------------------------- ### Rust FFI: Load Plugin with String Filename Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Implements FFI for loading a plugin by filename. It converts a C-style string (`*const c_char`) to a Rust string slice (`&str`) and handles potential conversion errors. ```rust // client/src/ffi.rs #[no_mangle] pub unsafe extern "C" fn plugin_manager_load_plugin( pm: *mut PluginManager, filename: *const c_char, ) -> c_int { let pm = &mut *pm; let filename = CStr::from_ptr(filename); let filename_as_str = match filename.to_str() { Ok(s) => s, Err(_) => { // TODO: proper error handling return -1; } }; // TODO: proper error handling and catch_unwind match pm.load_plugin(filename_as_str) { Ok(_) => 0, Err(_) => -1, } } ``` -------------------------------- ### Enable Qt Auto-Features Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/gui/CMakeLists.txt Enables automatic handling of Qt's Meta-Object Compiler (MOC), User Interface Compiler (UIC), and Resource Compiler (RCC). This simplifies Qt integration. ```cmake set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) ``` -------------------------------- ### Client Module CMakeLists.txt Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/setting_up.md Placeholder for the CMake configuration file for the Rust client module, typically located in the client/ directory. ```cmake # CMakeLists.txt ``` -------------------------------- ### C++ Wrapper: PluginManager Method Implementations Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Implements the C++ `PluginManager` wrapper methods. These methods call the corresponding Rust FFI functions, passing the necessary raw pointers and references. ```cpp // gui/wrappers.cpp PluginManager::PluginManager() { raw = ffi::plugin_manager_new(); } PluginManager::~PluginManager() { ffi::plugin_manager_destroy(raw); } void PluginManager::unload() { ffi::plugin_manager_unload(raw); } void PluginManager::pre_send(Request& req) { ffi::plugin_manager_pre_send(raw, req.raw); } void PluginManager::post_receive(Response& res) { ffi::plugin_manager_post_receive(raw, res.raw); } ``` -------------------------------- ### Build Rust Project Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/setting_up.md Compiles the Rust project using Cargo, producing a dynamic shared object library (e.g., libclient.so) in the target/debug directory. ```bash $ cargo build $ ls target/debug/ build deps examples incremental libclient.d libclient.so native ``` -------------------------------- ### Rust build script for cbindgen Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/cbindgen.md A build script that uses cbindgen to generate a C++ header file, placing it in the target directory and configuring a namespace. ```rust // client/build.rs extern crate cbindgen; use std::env; use std::path::PathBuf; use cbindgen::Config; fn main() { let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); let package_name = env::var("CARGO_PKG_NAME").unwrap(); let output_file = target_dir() .join(format!("{}.hpp", package_name)) .display() .to_string(); let config = Config { namespace: Some(String::from("ffi")), ..Default::default() }; cbindgen::generate_with_config(&crate_dir, config) .unwrap() .write_to_file(&output_file); } /// Find the location of the `target/` directory. Note that this may be /// overridden by `cmake`, so we also need to check the `CARGO_TARGET_DIR` /// variable. fn target_dir() -> PathBuf { if let Ok(target) = env::var("CARGO_TARGET_DIR") { PathBuf::from(target) } else { PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join("target") } } ``` -------------------------------- ### Integrating PluginManager into MainWindow (Header) Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Adds a `PluginManager` member variable to the `MainWindow` class in its header file, making it available for use throughout the main window's logic. ```diff // gui/main_window.hpp #include "wrappers.hpp" ... class MainWindow : public QMainWindow { ... private: ... PluginManager pm; }; ``` -------------------------------- ### Integrating PluginManager into MainWindow Request Sending Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Modifies the request sending process to include calls to `pm.pre_send(req)` before sending the request and `pm.post_receive(res)` after receiving the response, ensuring plugin hooks are triggered. ```cpp ... pm.pre_send(req); Response res = req.send(); pm.post_receive(res); ... ``` -------------------------------- ### Link Rust Library to C++ Executable Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/fun/problem_4/CMakeLists.txt Links the 'problem_4' executable against the compiled Rust library 'liblogging.so'. ```cmake target_link_libraries(problem_4 ${CMAKE_CURRENT_BINARY_DIR}/liblogging.so) ``` -------------------------------- ### Rust Request Constructor for FFI Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/basic_request.md Creates a new Rust Request object, taking a C-style string for the URL. Returns a null pointer on invalid input or URL parsing errors. Ensure the returned pointer is destroyed using `request_destroy`. ```rust // client/src/ffi.rs //! The foreign function interface which exposes this library to non-Rust //! languages. use std::ffi::CStr; use std::ptr; use libc::c_char; use reqwest::{Url, Method}; use Request; /// Construct a new `Request` which will target the provided URL and fill out /// all other fields with their defaults. /// /// # Note /// /// If the string passed in isn't a valid URL this will return a null pointer. /// /// # Safety /// /// Make sure you destroy the request with [`request_destroy()`] once you are /// done with it. /// /// [`request_destroy()`]: fn.request_destroy.html #[no_mangle] pub unsafe extern "C" fn request_create(url: *const c_char) -> *mut Request { if url.is_null() { return ptr::null_mut(); } let raw = CStr::from_ptr(url); let url_as_str = match raw.to_str() { Ok(s) => s, Err(_) => return ptr::null_mut(), }; let parsed_url = match Url::parse(url_as_str) { Ok(u) => u, Err(_) => return ptr::null_mut(), }; let req = Request::new(parsed_url, Method::Get); Box::into_raw(Box::new(req)) } ``` -------------------------------- ### C++ Program Using Rust Home Directory Function Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/fun/problems.md A C++ program that calls the Rust 'home_directory' function and prints the result. ```cpp // main.cpp #include extern "C" { char *home_directory(); } int main() { char* home = home_directory(); if (home == nullptr) { std::cout << "Unable to find the home directory" << std::endl; } else { std::cout << "Home directory is " << home << std::endl; } } ``` -------------------------------- ### PluginManager: Post-Receive Hook Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Iterates through plugins and fires their post_receive hook after receiving a response. Requires mutable access to the plugin list and response. ```rust for plugin in &mut self.plugins { trace!("Firing post_receive for {:?}", plugin.name()); plugin.post_receive(response); } ``` -------------------------------- ### Add C++ Executable and Link Library Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/fun/problem_2/CMakeLists.txt Adds a C++ executable named 'problem_2' and links it against the previously defined Rust shared library 'libfoo.so'. ```cmake add_executable(problem_2 ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp) target_link_libraries(problem_2 ${CMAKE_CURRENT_BINARY_DIR}/libfoo.so) ``` -------------------------------- ### Exposing FFI Module in Rust Library Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/basic_request.md Makes the `ffi` module publicly accessible from the main library entry point. ```rust // client/src/lib.rs pub mod ffi; ``` -------------------------------- ### Verify Rust function linkage with nm Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/setting_up.md Use the `nm` tool to inspect the symbols in a compiled library. Filter for symbols in the text section (T) to view exported functions like `hello_world`. ```bash $ nm libclient.so | grep ' T ' 0000000000003330 T hello_world <-- the function we created 00000000000096c0 T __rdl_alloc 00000000000098d0 T __rdl_alloc_excess 0000000000009840 T __rdl_alloc_zeroed 0000000000009760 T __rdl_dealloc 0000000000009a20 T __rdl_grow_in_place 0000000000009730 T __rdl_oom 0000000000009780 T __rdl_realloc 0000000000009950 T __rdl_realloc_excess 0000000000009a30 T __rdl_shrink_in_place 0000000000009770 T __rdl_usable_size 0000000000015ad0 T rust_eh_personality ``` -------------------------------- ### C++ Wrapper: PluginManager Class Definition Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Defines the C++ `PluginManager` wrapper class, declaring its constructor, destructor, and methods that mirror the Rust FFI functions. It also declares `Request` and `Response` as friends to allow access to their raw pointers. ```cpp // gui/wrappers.hpp class Request { friend class PluginManager; ... }; class Response { friend class PluginManager; ... }; class PluginManager { public: PluginManager(); ~PluginManager(); void unload(); void pre_send(Request& req); void post_receive(Response& res); private: ffi::PluginManager *raw; }; ``` -------------------------------- ### C++ Wrapper Implementation - Request send Method Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/send_basic.md Implements the `send()` method for the C++ Request class. It calls the Rust `request_send` function, checks for errors, and returns a new `Response` object. ```cpp // gui/wrappers.cpp extern "C" { ... void *request_send(void *); } Response Request::send() { void *raw_response = request_send(raw); if (raw_response == nullptr) { throw "Request failed"; } return Response(raw_response); } ``` -------------------------------- ### Link Rust Library to C++ Executable Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/setting_up.md Integrates the compiled Rust library into the C++ GUI executable by linking the library file. ```cmake set(SOURCE main.cpp) add_executable(gui ${SOURCE}) get_target_property(CLIENT_DIR client LOCATION) target_link_libraries(gui Qt5::Widgets) target_link_libraries(gui ${CLIENT_DIR}/libclient.so) add_dependencies(gui client) ``` -------------------------------- ### PluginManager: Pre-Send Hook Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Iterates through plugins and fires their pre_send hook before sending a request. Requires mutable access to the plugin list. ```rust for plugin in &mut self.plugins { trace!("Firing pre_send for {:?}", plugin.name()); plugin.pre_send(request); } ``` -------------------------------- ### C++ Request Wrapper Implementation Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/basic_request.md Implements the C++ 'Request' class, declaring external C functions for creation and destruction. It bridges `std::string` to C-style strings for the FFI calls. ```cpp // gui/wrappers.cpp #include "wrappers.hpp" extern "C" { void *request_create(const char *); void request_destroy(void *); } Request::Request(const std::string url) { raw = request_create(url.c_str()); if (raw == nullptr) { throw "Invalid URL"; } } Request::~Request() { request_destroy(raw); } ``` -------------------------------- ### Declare Plugin Export Macro Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md A convenience macro to export a `Plugin` implementation. It generates an `extern "C"` function `_plugin_create` that constructs and returns a raw pointer to the plugin object. ```rust // client/src/plugins.rs /// Declare a plugin type and its constructor. /// /// # Notes /// /// This works by automatically generating an `extern "C"` function with a /// pre-defined signature and symbol name. Therefore you will only be able to /// declare one plugin per library. #[macro_export] macro_rules! declare_plugin { ($plugin_type:ty, $constructor:path) => { #[no_mangle] pub extern "C" fn _plugin_create() -> *mut $crate::Plugin { // make sure the constructor is the correct type. let constructor: fn() -> $plugin_type = $constructor; let object = constructor(); let boxed: Box<$crate::Plugin> = Box::new(object); Box::into_raw(boxed) } }; } ``` -------------------------------- ### Update CMakeLists.txt for GUI Sources Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/setting_up.md Modify the CMakeLists.txt file to include the new source files for the GUI application. ```cmake set(SOURCE main_window.cpp main_window.hpp main.cpp) ``` -------------------------------- ### C++ Program Using Rust Adder Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/fun/problems.md A C++ program that links against and calls the Rust 'add' function to perform addition. ```cpp // main.cpp #include #include extern "C" { uint32_t add(uit32_t, uit32_t); } int main() { uint32_t a = 5, b = 10; uint32_t sum = add(a, b); std::cout << "The sum of " << a << " and " << b << " is " << sum << std::endl; } ``` -------------------------------- ### Fire Plugin Pre-Send Hooks Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Iterates through all loaded plugins and calls their `pre_send` hook, allowing each plugin to inspect or modify the outgoing request before it is sent. ```rust // client/src/plugins.rs /// Iterate over the plugins, running their `pre_send()` hook. pub fn pre_send(&mut self, request: &mut Request) { debug!("Firing pre_send hooks"); ``` -------------------------------- ### Define Rust Library Path Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/setting_up.md Defines the expected path for the compiled Rust shared library (.so) based on the determined target directory. ```cmake set(CLIENT_SO "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_DIR}/libclient.so") ``` -------------------------------- ### Include Plugin in Main CMakeLists.txt Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Adds the injector-plugin directory to the main CMake build configuration to ensure it is compiled. ```diff # ./CMakeLists.txt add_subdirectory(client) + add_subdirectory(injector-plugin) add_subdirectory(gui) ``` -------------------------------- ### Declare Plugin Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Declares the Injector plugin using the `declare_plugin!()` macro, making it available for dynamic loading. ```rust // injector-plugin/src/lib.rs declare_plugin!(Injector, Injector::default); ``` -------------------------------- ### Define Rust Library and C++ Executable Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/fun/problem_3/CMakeLists.txt Configures a Rust library and a C++ executable, linking the library to the executable. Ensures the executable depends on the library's build process. ```cmake rustc_library(problem_3_lib ${CMAKE_CURRENT_SOURCE_DIR}/home.rs ${CMAKE_CURRENT_BINARY_DIR}/libhome.so) add_executable(problem_3 ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp) target_link_libraries(problem_3 ${CMAKE_CURRENT_BINARY_DIR}/libhome.so) add_dependencies(problem_3 problem_3_lib) ``` -------------------------------- ### Rust Library with Panic Function Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/fun/problems.md A Rust library exporting a C-compatible function 'foo' that intentionally panics. ```rust // foo.rs #[no_mangle] pub extern "C" fn foo() { panic!("Oops..."); } ``` -------------------------------- ### Add Dependency Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/fun/problem_2/CMakeLists.txt Ensures that the 'problem_2' executable depends on the 'problem_2_lib' target, guaranteeing the library is built before the executable. ```cmake add_dependencies(problem_2 problem_2_lib) ``` -------------------------------- ### Find Qt5 Widgets Module Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/gui/CMakeLists.txt Locates and imports the Qt5 Widgets module. This makes Qt Widgets classes and functions available for use in the project. ```cmake find_package(Qt5Widgets) ``` -------------------------------- ### Define the HTTP Request struct Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/client.md Define a `Request` struct to encapsulate all necessary information for an HTTP request, including destination, method, headers, cookies, and body. ```rust // client/src/request.rs use cookie::CookieJar; use reqwest::{self, Method, Url}; use reqwest::header::{Cookie, Headers}; /// A HTTP request. #[derive(Debug, Clone)] pub struct Request { pub destination: Url, pub method: Method, pub headers: Headers, pub cookies: CookieJar, pub body: Option>, } ``` -------------------------------- ### Define the HTTP Response struct Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/client.md Define a simplified `Response` struct for use by the C++ GUI, containing headers, body, and status code. ```rust // client/src/response.rs use std::io::Read; use reqwest::{self, StatusCode}; use reqwest::header::Headers; use errors::*; #[derive(Debug, Clone)] pub struct Response { pub headers: Headers, pub body: Vec, pub status: StatusCode, } impl Response { pub(crate) fn from_reqwest(original: reqwest::Response) -> Result { let mut original = original.error_for_status()?; let headers = original.headers().clone(); let status = original.status(); let mut body = Vec::new(); original .read_to_end(&mut body) .chain_err(|| "Unable to read the response body")?; Ok(Response { status, body, headers, }) } } ``` -------------------------------- ### Define Rust Library Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/fun/problem_2/CMakeLists.txt Defines a Rust library named 'problem_2_lib' from the source file 'foo.rs' and specifies the output shared library name as 'libfoo.so'. ```cmake rustc_library(problem_2_lib ${CMAKE_CURRENT_SOURCE_DIR}/foo.rs ${CMAKE_CURRENT_BINARY_DIR}/libfoo.so) ``` -------------------------------- ### MainWindow Header with closeEvent Override Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/dynamic_loading.md Updates the `MainWindow` header file to declare the overridden `closeEvent` method, preparing for its implementation in the `.cpp` file. ```cpp // gui/main_window.hpp class MainWindow : public QMainWindow { ... protected: void closeEvent(QCloseEvent *event) override; ... }; ``` -------------------------------- ### Update Rust FFI for Error Handling Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/errors/return_types.md Refactors the `request_create` function in Rust FFI bindings to include calls to `update_last_error()` for various error conditions, such as null URLs, invalid UTF-8, and URL parsing failures. ```diff // client/src/ffi.rs #[no_mangle] pub unsafe extern "C" fn request_create(url: *const c_char) -> *mut Request { if url.is_null() { + let err = Error::from("No URL provided"); + update_last_error(err); return ptr::null_mut(); } let raw = CStr::from_ptr(url); let url_as_str = match raw.to_str() { Ok(s) => s, - Err(_) => return ptr::null_mut(), + Err(e) => { + let err = Error::with_chain(e, "Unable to convert URL to a UTF-8 string"); + update_last_error(err); + return ptr::null_mut(); + } }; let parsed_url = match Url::parse(url_as_str) { Ok(u) => u, - Err(_) => return ptr::null_mut(), + Err(e) => { + let err = Error::with_chain(e, "Unable to parse the URL"); + update_last_error(err); + return ptr::null_mut(); + } }; ... ``` -------------------------------- ### Configure Rust Build and Target Directory Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/setting_up.md Sets the Cargo build command and target directory based on the CMake build type (Debug or Release). This ensures correct optimization levels and debug symbols. ```cmake if (CMAKE_BUILD_TYPE STREQUAL "Debug") set(CARGO_CMD cargo build) set(TARGET_DIR "debug") else () set(CARGO_CMD cargo build --release) set(TARGET_DIR "release") endif () ``` -------------------------------- ### Writing Error Messages to a Caller-Provided Buffer Source: https://github.com/michael-f-bryan/rust-ffi-guide/blob/master/book/errors/return_types.md This snippet demonstrates writing an error message into a buffer provided by the caller. It includes checks for buffer size and appends a null terminator for C-style string compatibility. This pattern simplifies memory management for FFI. ```rust warn!("Buffer provided for writing the last error message is too small."); warn!( "Expected at least {} bytes but got {}", error_message.len() + 1, buffer.len() ); return -1; } ptr::copy_nonoverlapping( error_message.as_ptr(), buffer.as_mut_ptr(), error_message.len(), ); // Add a trailing null so people using the string as a `char *` don't // accidentally read into garbage. buffer[error_message.len()] = 0; error_message.len() as c_int } ```