Giao diện
FFI & PyO3 Interop
Rust ↔ C ↔ Python Interoperability
1. Foreign Function Interface (FFI)
#[repr(C)] Layout
rust
// Rust struct với C-compatible layout
#[repr(C)]
pub struct Point {
pub x: f64,
pub y: f64,
}
// Memory layout giống hệt C:
// struct Point {
// double x;
// double y;
// };Exporting Functions to C
rust
// lib.rs
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
a + b
}
#[no_mangle]
pub extern "C" fn point_distance(p1: *const Point, p2: *const Point) -> f64 {
unsafe {
let p1 = &*p1;
let p2 = &*p2;
let dx = p2.x - p1.x;
let dy = p2.y - p1.y;
(dx * dx + dy * dy).sqrt()
}
}toml
# Cargo.toml
[lib]
crate-type = ["cdylib"] # Creates .so/.dll/.dylibCalling C from Rust
rust
// Declare external C functions
extern "C" {
fn printf(format: *const i8, ...) -> i32;
fn malloc(size: usize) -> *mut u8;
fn free(ptr: *mut u8);
}
fn main() {
unsafe {
let msg = b"Hello from Rust!\n\0";
printf(msg.as_ptr() as *const i8);
}
}Safety Guidelines
| Pattern | Safe? | Notes |
|---|---|---|
extern "C" fn | ✅ | Exporting is safe |
| Calling C | ❌ | Must wrap in unsafe |
| Raw pointers | ❌ | Validate before deref |
| Callbacks | ⚠️ | Beware of panics |
2. PyO3: Python Extensions
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ PYTHON APPLICATION │
├─────────────────────────────────────────────────────────────────┤
│ import my_rust_module │
│ result = my_rust_module.fast_function(data) │
└───────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ PyO3 BINDINGS │
│ • Type conversion (PyObject ↔ Rust types) │
│ • GIL management │
│ • Exception handling │
└───────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ RUST CODE │
│ • Pure Rust logic (no Python dependencies) │
│ • Full performance, memory safety │
└─────────────────────────────────────────────────────────────────┘Basic PyO3 Setup
toml
# Cargo.toml
[package]
name = "my_rust_module"
version = "0.1.0"
edition = "2021"
[lib]
name = "my_rust_module"
crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.20", features = ["extension-module"] }rust
// src/lib.rs
use pyo3::prelude::*;
/// Formats the sum of two numbers as string
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
Ok((a + b).to_string())
}
/// A Python module implemented in Rust
#[pymodule]
fn my_rust_module(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
Ok(())
}Build & Use
bash
# Install maturin
pip install maturin
# Build and install
maturin develop --release
# Use in Python
python -c "import my_rust_module; print(my_rust_module.sum_as_string(5, 3))"
# Output: "8"3. GIL Management
The Problem
PYTHON GIL (Global Interpreter Lock)
┌─────────────────────────────────────────────────────────────────┐
│ │
│ Thread 1 Thread 2 Thread 3 │
│ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │Python │ │Python │ │Python │ │
│ │ code │ │ code │ │ code │ │
│ └───┬───┘ └───┬───┘ └───┬───┘ │
│ │ │ │ │
│ └──────────────────────────────────── │
│ │ │
│ ┌────┴────┐ │
│ │ GIL │ ← Only ONE thread at a time │
│ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘Releasing GIL in Rust
rust
use pyo3::prelude::*;
/// CPU-intensive function that releases the GIL
#[pyfunction]
fn compute_heavy(data: Vec<f64>) -> PyResult<f64> {
// Release the GIL during Rust computation
Python::with_gil(|py| {
py.allow_threads(|| {
// This runs WITHOUT the GIL!
// Other Python threads can run
heavy_computation(&data)
})
})
}
fn heavy_computation(data: &[f64]) -> f64 {
data.iter().map(|x| x.sin().cos().tan()).sum()
}When to Release GIL
| Operation | Release GIL? | Reason |
|---|---|---|
| Pure Rust compute | ✅ Yes | No Python objects accessed |
| File I/O | ✅ Yes | Blocking operation |
| Network I/O | ✅ Yes | Blocking operation |
| Accessing PyObject | ❌ No | GIL required |
| Calling Python | ❌ No | GIL required |
4. Type Conversions
Python → Rust
rust
use pyo3::prelude::*;
use pyo3::types::{PyList, PyDict};
#[pyfunction]
fn process_list(py: Python, list: &PyList) -> PyResult<Vec<i64>> {
list.iter()
.map(|item| item.extract::<i64>())
.collect()
}
#[pyfunction]
fn process_dict(dict: &PyDict) -> PyResult<()> {
for (key, value) in dict.iter() {
let key: String = key.extract()?;
let value: i64 = value.extract()?;
println!("{}: {}", key, value);
}
Ok(())
}Rust → Python
rust
use pyo3::prelude::*;
#[pyfunction]
fn create_list(py: Python) -> PyResult<Py<PyList>> {
let list = PyList::new(py, &[1, 2, 3, 4, 5]);
Ok(list.into())
}
#[pyclass]
struct Point {
x: f64,
y: f64,
}
#[pymethods]
impl Point {
#[new]
fn new(x: f64, y: f64) -> Self {
Point { x, y }
}
fn distance(&self, other: &Point) -> f64 {
let dx = self.x - other.x;
let dy = self.y - other.y;
(dx * dx + dy * dy).sqrt()
}
}5. Error Handling
rust
use pyo3::prelude::*;
use pyo3::exceptions::PyValueError;
#[pyfunction]
fn divide(a: f64, b: f64) -> PyResult<f64> {
if b == 0.0 {
Err(PyValueError::new_err("Division by zero"))
} else {
Ok(a / b)
}
}
// Custom exception
pyo3::create_exception!(my_module, CustomError, pyo3::exceptions::PyException);
#[pyfunction]
fn risky_operation() -> PyResult<()> {
Err(CustomError::new_err("Something went wrong"))
}6. Complete Example: Fast CSV Parser
rust
use pyo3::prelude::*;
use std::fs::File;
use std::io::{BufRead, BufReader};
#[pyfunction]
fn parse_csv_fast(path: String) -> PyResult<Vec<Vec<String>>> {
Python::with_gil(|py| {
py.allow_threads(|| {
let file = File::open(&path)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyIOError, _>(e.to_string()))?;
let reader = BufReader::new(file);
let mut rows = Vec::new();
for line in reader.lines() {
let line = line
.map_err(|e| PyErr::new::<pyo3::exceptions::PyIOError, _>(e.to_string()))?;
let fields: Vec<String> = line.split(',')
.map(|s| s.trim().to_string())
.collect();
rows.push(fields);
}
Ok(rows)
})
})
}
#[pymodule]
fn fast_csv(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(parse_csv_fast, m)?)?;
Ok(())
}python
# Usage in Python
import fast_csv
import time
start = time.time()
data = fast_csv.parse_csv_fast("large_file.csv")
print(f"Parsed {len(data)} rows in {time.time() - start:.2f}s")🎯 Best Practices
| Practice | Reason |
|---|---|
| Release GIL for CPU work | Allow Python parallelism |
Use #[repr(C)] for FFI | Memory layout compatibility |
| Handle errors properly | Don't panic across FFI boundary |
| Batch operations | Reduce FFI call overhead |