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
}

🧠 Test Your Knowledge

What attribute is needed to prevent Rust from mangling function names for C compatibility?