``c_void *`` for opaque FFI types

I’m dealing with code that exposes an opaque C++ value to Rust through a void
pointer. Intuitively I’d expect a pointer to a zero sized struct in this
context as recommended by the Nomicon.

Will this make any difference in practice assuming those pointers are just
pushed around by Rust code, never actually dereferenced or taken the value
size of?

If you create the pointer with Box::into_raw(Box::new(zero_sized)) and make sure to cast the void pointer back to the same zero-sized type, then you're all good.

If you create the pointer with Box::into_raw(Box::new(zero_sized)) and make
sure to cast the void pointer back to the same zero-sized type, then you're
all good.

In this case the pointer is created by C++. Which then calls into the Rust code
which itself calls into some other C++. The backing memory and resources are
owned by the outer layer C++ code and just passed through to the inner layer
by means of a pointer.

Oh, you mean how to represent a C++ void pointer in Rust? Just use a pointer to c_void.

1 Like

I should probably have provided more context; I’m not just dealing with the one
opaque struct but a whole zoo of them, often used in close proximity.

Using c_void everywhere has the drawback of rendering those pointers
indistinguishable on a type level. After some meditating on the issue I think
converting the API to a pointer-to-zero-sized-struct is preferable.

The pattern used by bindgen is to create a type that can't be instantiated by external code. That way you get the safety of using different types, but need to only ever pass it around behind a raw pointer and pinky swear to never dereference it.

So a header with some opaque type (e.g. a C++ class or opaque struct) like this:

// header.cpp

struct Foo;

extern "C" {
  Foo *new_foo();
  void free_foo(Foo *foo);
}

Would be given these bindings:

/* automatically generated by rust-bindgen 0.56.0 */

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct Foo {
    _unused: [u8; 0],
}
extern "C" {
    pub fn new_foo() -> *mut Foo;
}
extern "C" {
    pub fn free_foo(foo: *mut Foo);
}

When RFC 1861 lands we'll be able to use proper "extern" types, so instead of creating a struct with an _unused field you'll be able to write extern "C" { type Foo; } and the compiler will make sure you can't do anything with Foo except pass it around behind a pointer.

https://rust-lang.github.io/rfcs/1861-extern-types.html

1 Like

When RFC 1861 lands we'll be able to use proper "extern" types,
so instead of creating a struct with an _unused field you'll be
able to write extern "C" { type Foo; } and the compiler will
make sure you can't do anything with Foo except pass it around
behind a pointer.

I’m aware of the RFC and since I’m dealing with FFI on the
regular, I hope for it to land rather sooner than later.