Foreign Function Interface (FFI) in Rust allows you to call functions written in other programming languages, such as C, from Rust, and vice versa. This is particularly useful for integrating Rust with existing codebases or leveraging libraries written in other languages.
Key Concepts
- Extern Block: Used to declare functions from other languages.
- C ABI (Application Binary Interface): The most common ABI used for FFI in Rust.
- Unsafe Code: FFI often involves unsafe code because Rust cannot guarantee the safety of foreign functions.
Extern Block
An extern
block is used to declare functions that are implemented in another language. Here’s a simple example of calling a C function from Rust.
Example: Calling a C Function from Rust
First, let's write a simple C function:
Next, compile this C code into a shared library:
Now, let's call this C function from Rust:
Explanation
extern "C"
: Specifies that the functions inside the block use the C ABI.unsafe
: Calling foreign functions is inherently unsafe because Rust cannot enforce safety guarantees.
Compiling and Running
To compile and run the Rust code, you need to link the C library:
Passing Data Between Rust and C
You can pass data between Rust and C, but you need to be careful with data types and memory management.
Example: Passing an Integer
C Code:
Compile the C code:
Rust Code:
// main.rs extern "C" { fn add(a: i32, b: i32) -> i32; } fn main() { let result = unsafe { add(5, 3) }; println!("5 + 3 = {}", result); }
Explanation
fn add(a: i32, b: i32) -> i32;
: Declares the C function in Rust.unsafe { add(5, 3) }
: Calls the C function within an unsafe block.
Compiling and Running
Practical Exercise
Exercise: Calling a C Function that Takes a String
- Write a C function that takes a string and prints it.
- Compile the C code into a shared library.
- Write Rust code to call this C function, passing a Rust string.
C Code
Compile the C code:
Rust Code
// main.rs use std::ffi::CString; use std::os::raw::c_char; extern "C" { fn print_string(s: *const c_char); } fn main() { let rust_string = CString::new("Hello from Rust!").expect("CString::new failed"); let c_string = rust_string.as_ptr(); unsafe { print_string(c_string); } }
Explanation
CString::new("Hello from Rust!")
: Converts a Rust string to a C-compatible string.as_ptr()
: Gets a raw pointer to the C string.unsafe { print_string(c_string) }
: Calls the C function with the C string.
Compiling and Running
Common Mistakes and Tips
- Memory Management: Be cautious with memory allocation and deallocation when passing data between Rust and C.
- Data Types: Ensure that the data types match between Rust and C.
- Safety: Always use
unsafe
blocks when calling foreign functions and handle potential errors.
Conclusion
In this section, you learned how to use Rust's FFI to call functions written in C. You explored the basics of extern
blocks, passing data between Rust and C, and handling strings. Understanding FFI is crucial for integrating Rust with other languages and leveraging existing libraries. In the next module, we will delve into building a project and best practices in Rust.