🎓 A1Learner
🔍

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?