In this lesson, you will learn how to safely pass ownership of heap-allocated data across the Foreign Function Interface (FFI) boundary between Rust and C.
When you pass data from Rust to C, the primary challenge is that C does not understand Rust's Box or Vec types. Rust’s ownership model expects the Drop trait to run when a value goes out of scope, but C is oblivious to these rules. If you pass a pointer to C and leave it there, Rust might try to deallocate that memory while C is still using it, or conversely, memory might leak forever because C doesn't know how to clean it up.
The "Transfer of Responsibility" Pattern
To coordinate ownership, you must treat the FFI boundary as a formal handoff. You can use Box::into_raw to turn a heap-allocated object into a raw pointer, effectively "leaking" it from Rust’s control. Once it is a raw pointer, Rust no longer tracks its lifecycle.
#[no_mangle]
pub extern "C" fn create_data() -> *mut MyStruct {
let boxed = Box::new(MyStruct { value: 42 });
// Relinquish ownership to the C side
Box::into_raw(boxed)
}
#[no_mangle]
pub extern "C" fn free_data(ptr: *mut MyStruct) {
if !ptr.is_null() {
// Reclaim ownership and trigger the destructor
unsafe { drop(Box::from_raw(ptr)); }
}
}
In this example, create_data hands the pointer to C. The Rust compiler stops worrying about that memory. However, you must provide a corresponding free_data function. C code is expected to call this function when it is finished. If C fails to call free_data, the memory leaks; if C tries to use the pointer after calling free_data, you encounter a use-after-free bug.
Because Rust cannot enforce these rules inside C, you must ensure that the "ownership contract" is clearly documented: whoever receives the raw pointer assumes the responsibility of either destroying it or returning it to Rust for destruction. You are effectively extending Rust's move semantics manually across the language barrier.
Key Takeaway: Crossing the FFI boundary requires manual memory management where you must explicitly surrender ownership using raw pointers and provide a coordinated destruction path for the other language to follow.