Rust FFI (Foreign Function Interface)
Interfacing Rust with other programming languages
🔗 What is Rust FFI?
FFI (Foreign Function Interface) allows Rust to call functions from other languages like C, Python, and JavaScript, and vice versa. This enables code reuse, gradual migration, and integration with existing systems.
// Calling C function from Rust
extern "C" {
fn printf(format: *const i8, ...) -> i32;
}
unsafe {
printf(b"Hello from C!\n\0".as_ptr() as *const i8);
}
Output:
Hello from C!
FFI Directions
Calling C from Rust
Use existing C libraries in Rust
extern "C" {
fn strlen(s: *const i8) -> usize;
}
Calling Rust from C
Expose Rust functions to C
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
a + b
}
Python Integration
Create Python modules with PyO3
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> String {
(a + b).to_string()
}
WebAssembly
Run Rust in web browsers
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
🔹 Calling C Functions from Rust
Link and use C libraries in your Rust code:
// Using libc crate for standard C functions
use libc::{c_char, c_int, strlen, malloc, free};
use std::ffi::{CString, CStr};
fn main() {
// Create a C-compatible string
let c_string = CString::new("Hello, C world!").unwrap();
// Call C strlen function
let length = unsafe {
strlen(c_string.as_ptr())
};
println!("String length: {}", length);
// Working with C malloc/free
unsafe {
let ptr = malloc(100) as *mut c_char;
if !ptr.is_null() {
// Use the allocated memory
println!("Allocated 100 bytes at: {:p}", ptr);
free(ptr as *mut libc::c_void);
}
}
}
Add to Cargo.toml:
[dependencies]
libc = "0.2"
🔹 Creating a C-Compatible Library
Expose Rust functions for use in C programs:
# Cargo.toml
[lib]
name = "mylib"
crate-type = ["cdylib", "staticlib"]
// lib.rs
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int};
/// Add two integers
#[no_mangle]
pub extern "C" fn add(a: c_int, b: c_int) -> c_int {
a + b
}
/// Calculate factorial
#[no_mangle]
pub extern "C" fn factorial(n: c_int) -> c_int {
if n <= 1 {
1
} else {
n * factorial(n - 1)
}
}
/// Process a C string
#[no_mangle]
pub extern "C" fn process_string(input: *const c_char) -> *mut c_char {
if input.is_null() {
return std::ptr::null_mut();
}
let c_str = unsafe { CStr::from_ptr(input) };
let rust_str = match c_str.to_str() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
let processed = format!("Processed: {}", rust_str.to_uppercase());
let c_string = CString::new(processed).unwrap();
c_string.into_raw()
}
/// Free string allocated by process_string
#[no_mangle]
pub extern "C" fn free_string(s: *mut c_char) {
if !s.is_null() {
unsafe {
let _ = CString::from_raw(s);
}
}
}
C Header (mylib.h):
int add(int a, int b);
int factorial(int n);
char* process_string(const char* input);
void free_string(char* s);
🔹 Python Extension with PyO3
Create Python modules using Rust:
# Cargo.toml
[lib]
name = "string_sum"
crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.20", features = ["extension-module"] }
// lib.rs
use pyo3::prelude::*;
/// Formats the sum of two numbers as string.
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult {
Ok((a + b).to_string())
}
/// Count words in a string
#[pyfunction]
fn count_words(text: &str) -> PyResult {
Ok(text.split_whitespace().count())
}
/// A Python class
#[pyclass]
struct Counter {
#[pyo3(get, set)]
count: usize,
}
#[pymethods]
impl Counter {
#[new]
fn new() -> Self {
Counter { count: 0 }
}
fn increment(&mut self) {
self.count += 1;
}
fn get_count(&self) -> usize {
self.count
}
}
/// A Python module implemented in Rust.
#[pymodule]
fn string_sum(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
m.add_function(wrap_pyfunction!(count_words, m)?)?;
m.add_class::()?;
Ok(())
}
Python Usage:
import string_sum
result = string_sum.sum_as_string(5, 20)
print(result) # "25"
words = string_sum.count_words("Hello world from Rust")
print(words) # 4
counter = string_sum.Counter()
counter.increment()
print(counter.count) # 1
🔹 WebAssembly with wasm-bindgen
Run Rust code in web browsers:
# Cargo.toml
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
[dependencies.web-sys]
version = "0.3"
features = [
"console",
"Document",
"Element",
"HtmlElement",
"Window",
]
// lib.rs
use wasm_bindgen::prelude::*;
// Import the `console.log` function from the browser
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
// Define a macro for easier console logging
macro_rules! console_log {
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}
#[wasm_bindgen]
pub fn greet(name: &str) {
console_log!("Hello, {}!", name);
}
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[wasm_bindgen]
pub struct Calculator {
value: f64,
}
#[wasm_bindgen]
impl Calculator {
#[wasm_bindgen(constructor)]
pub fn new() -> Calculator {
Calculator { value: 0.0 }
}
#[wasm_bindgen(getter)]
pub fn value(&self) -> f64 {
self.value
}
pub fn add(&mut self, other: f64) {
self.value += other;
}
pub fn multiply(&mut self, other: f64) {
self.value *= other;
}
}
JavaScript Usage:
import init, { greet, add, Calculator } from './pkg/my_wasm.js';
async function run() {
await init();
greet('WebAssembly');
console.log(add(1, 2)); // 3
let calc = new Calculator();
calc.add(10);
calc.multiply(2);
console.log(calc.value); // 20
}