Rust FFI Opaque Types
An opaque type is a C type whose insides Rust never sees. Rust passes the pointer back to C and never looks inside.
A type Rust may not look inside
The C header names the type and nothing more. The fields stay secret.
typedef struct Counter Counter;The Rust mirror
Rust declares the same name with an empty private field. The struct has no real body.
#[repr(C)]
pub struct Counter {
_private: [u8; 0],
}C makes it, C destroys it
Two C functions guard the life of the value. One builds it. One frees it.
unsafe extern "C" {
fn counter_new(start: i32) -> *mut Counter;
fn counter_free(c: *mut Counter);
}Check the pointer first
A factory can fail. It answers with null when it cannot build the value.
let counter = unsafe { counter_new(10) };
if counter.is_null() {
println!("C could not make a counter");
return;
}Pass it back, never open it
Rust hands the same pointer to the next C call. The pointer is a ticket, not a box to open.
unsafe {
counter_add(counter, 5);
println!("total = {}", counter_get(counter));
}Example
use std::ffi::c_int;
#[repr(C)]
pub struct Counter {
_private: [u8; 0],
}
unsafe extern "C" {
fn counter_new(start: c_int) -> *mut Counter;
fn counter_add(c: *mut Counter, amount: c_int);
fn counter_get(c: *const Counter) -> c_int;
fn counter_free(c: *mut Counter);
}
fn main() {
let counter = unsafe { counter_new(10) };
if counter.is_null() {
println!("C could not make a counter");
return;
}
unsafe {
counter_add(counter, 5);
counter_add(counter, 3);
println!("total = {}", counter_get(counter));
counter_free(counter);
}
}Key points
- Opaque type: a C type whose fields Rust never sees.
- Rust holds only a pointer to it
- Declare it as a struct with a zero-sized private field.
- The declaration gives the pointer a name and a type
- Use C factory functions to create and destroy it: one C function makes it, another frees it
- Check every new pointer for null before you use it.
- A null return is how C says the creation failed
- Only C code may read or write the fields.
- Rust code just carries the pointer between C calls
- Never invent the fields yourself and read them from Rust.
- The C side may change its layout in the next version.
In this example, counter_new asks C to build a counter that starts at 10. Rust receives only a pointer. The value itself stays hidden inside C memory. Rust checks the pointer for null. Then it passes the pointer back into counter_add twice. counter_get reads the total through C, and Rust prints total = 18. At the end, counter_free gives the memory back to C. A tiny C library provides the four functions. It keeps the struct fields private. Real libraries ship exactly this shape: a handle, a few functions, and no visible fields.
Note
Your code would then read the wrong bytes in silence
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
What may Rust code do with an opaque type?