Course contentsShow
← Rust
Lesson 582 of 1,9105. Type System and TraitsFree lesson

Implementing traits for C-FFI types

Create Rust-idiomatic wrappers for opaque C pointers using local trait traits.

In this lesson, you will learn how to bridge the gap between "unsafe" C pointers and idiomatic Rust by implementing traits for opaque C-FFI types.

The Challenge of Opaque Pointers

When working with C libraries via FFI (Foreign Function Interface), you often receive an opaque pointer, typically represented in Rust as *mut c_void or a repr(C) struct that acts as a handle. Because these types don't "know" anything about Rust's safety guarantees, they don't automatically implement standard traits like Drop, Clone, or Send.

To turn these raw handles into safe, idiomatic Rust, we create a "wrapper" struct and implement traits to manage their lifecycle and behavior.

Creating a Safe Wrapper

Suppose a C library gives us a DatabaseHandle. We define a wrapper that holds the pointer and ensures Rust manages its memory:

use std::ffi::c_void;

// The raw FFI type
#[repr(C)]
pub struct RawDb { _unused: [u8; 0] }

// Our idiomatic Rust wrapper
pub struct Database( *mut RawDb );

// We implement Drop to ensure the C-library's 'close' function is called
impl Drop for Database {
    fn drop(&mut self) {
        unsafe { ffi::db_close(self.0); }
    }
}

Adding Behavioral Traits

Once we have a wrapper, we can implement traits to make the type feel "native." For example, if our database has a unique ID, we might implement PartialEq by comparing the raw pointers:

impl PartialEq for Database {
    fn eq(&self, other: &Self) -> bool {
        self.0 == other.0
    }
}

// Ensure the type is safe to share between threads
unsafe impl Send for Database {}

By wrapping the pointer in a struct and implementing traits like Drop, Send, or even AsRef, you transition from manual, error-prone pointer arithmetic to Rust's type-safe ecosystem. The trait implementation acts as the "contract" that ensures the C-side resource is handled correctly whenever the Rust object is created, moved, or destroyed.

Key Takeaway: You bridge the safety gap of C-FFI by wrapping raw pointers in a Rust struct and implementing standard traits to control their lifetime, thread safety, and equality semantics.