Course contentsShow
← Rust
Lesson 1469 of 1,91012. Unsafe Rust and Low-Level AccessFree lesson

FFI Lifetime Management

You will bridge the gap between C's manual memory and Rust's ownership by strictly defining object lifetimes.

In this lesson, you will learn how to enforce Rust’s memory safety guarantees when bridging the gap between C’s manual lifecycle management and Rust’s strict ownership model.

When you pass a pointer from Rust to C, or vice versa, the Rust compiler loses visibility into the memory’s lifecycle. C expects you to manage the pointer manually, but Rust expects a clear ownership hierarchy. To bridge this, we use lifetimes to signal to the Rust borrow checker that a pointer held by C remains valid for a specific scope.

The Problem: Dangling Pointers

If you wrap a C struct in a Rust object, Rust might drop the object and free the memory while the C library still holds a reference to it. Without explicit lifetime annotations, the compiler cannot know that the C library is a "consumer" of your data.

Bridging with Lifetimes

You can link the lifetime of a Rust wrapper struct to the data it points to. Consider this pattern:

struct CWrapper<'a> {
    inner: *mut c_void,
    _marker: PhantomData<&'a T>, // Ties the lifetime to type T
}

impl<'a> CWrapper<'a> {
    pub fn new(data: &'a mut T) -> Self {
        CWrapper {
            inner: data as *mut T as *mut c_void,
            _marker: PhantomData,
        }
    }
}

By using PhantomData<&'a T>, you inform the borrow checker that CWrapper holds a reference to T with lifetime 'a. Even though the pointer inside inner is technically an untyped *mut c_void (which the compiler treats as having an unbounded lifetime), the PhantomData forces the compiler to treat the wrapper as if it borrows T.

If you attempt to drop the underlying T while the CWrapper is still in scope, the compiler will catch the error, preventing a use-after-free scenario in the FFI layer. When designing these interfaces, always treat the raw pointer as an extension of the Rust object’s scope; if the C code stores the pointer for later, you must ensure the Rust-side backing memory is not dropped by pinning it or wrapping it in a structure that outlives the C usage.

Key Takeaway: Use PhantomData to anchor your raw pointers to Rust lifetimes, ensuring the borrow checker enforces that your managed data outlives any C-side access.