Recipe
Hand a Rust object to C as an opaque handle
Use when: C code must create, use and destroy an object whose type is a Rust struct
Goal: C holds only a pointer, every operation goes through Rust, and the object is freed exactly once by Rust's allocator
Taught in Ownership across the boundary
Quick recipe
-
1
Export
new, which builds the object in aBoxand returnsBox::into_raw(b).Example
Box::into_raw(Box::new(Playlist { tracks: Vec::new() }))Why: into_raw gives up ownership without freeing, so the object outlives the call and C holds the duty to return it.
-
2
In the header, write only
typedef struct Name Name;and pointer-taking prototypes.Example
typedef struct Playlist Playlist;Why: C can hold and pass the pointer but can't read or copy the object, so its layout stays Rust's business (no repr(C) needed).
-
3
Export
unsafe extern "C" fns that take the handle, turn it into&mut *handleor&*handle, and never keep C's pointers.Example
playlist_add(list, title)copiestitleinto aString.Why: only Rust interprets the object, and copying C's data in keeps the object free of pointers into C's memory.
-
4
Export
free, which ignores null and otherwise dropsBox::from_raw(handle); document that the handle must not be used afterwards.Example
drop(unsafe { Box::from_raw(list) });Why: from_raw restores the Box, and dropping it frees the object with the allocator that made it, and only once.
Common mistakes
- Letting C call
free()on the handle: wrong allocator, and the object's own heap data (the Vec, the Strings) would leak. - Forgetting the free function entirely, so every handle leaks.
- Calling
Box::from_rawin any function other than the free function: that drop would free the object while C still holds the handle.