What's the difference between a usize and a pointer for opaque FFI types?

A C library might return an opaque structure with the type void*. In Rust, for LLVM reasons, this might be expressed as *mut i8 (at least until extern types are stable).

However an opaque pointer should never be dereferenced by the Rust program, only the C library should do so. The Rust side only interacts with it by passing it between the library functions. And it might not even be a pointer at all. For example, it could be an offset into a lookup table where the real pointer is stored.

Given all that, what would be the difference between using:

pub struct Opaque(*mut i8);

And using a usize, which is always the exact same byte width as a pointer:

pub struct Opaque(usize);

In neither case would it be correct to operate directly on the inner data, except perhaps to print the value in debug messages. So is there a difference in how Rust handles the two cases in this scenario?

The Reference guarantees that usize has the same size and alignment as a pointer. This is supplemented by Notes on Type Layouts and ABIs in Rust which further notes that pointers and integer types have the same type-kind (“though this may change in the future”), and are therefore ABI-compatible. So there should be no difference for FFI purposes, at least on current platforms.

Lucky for Rust that it doesn't support m68k, which has separate CPU registers for data and addresses, and presumably a different ABIs for 32bit pointers and 32bit integers.

The usual way is to use *mut Opaque where Opaque is some impossible-to-construct Rust type for FFI (sys crates), and non-transparent Rust wrappers for higher level APIs that explicitly pass self.0 to FFI.

If you put any struct with at least one private type in its own module then surely it's un-constructable? At least through normal means.

That is to say, I know what is usually done but I don't quite understand why it must be done that way.

The why part is partly probably a matter of taste.

  • *mut Foo is definitely ABI-compatible, because the pointee type doesn't influence the ABI in C, so the whole question about [repr(transparent)] never arises.

  • *mut Foo clearly corresponds to C struct Foo*, but an opaque usize-type may leave someone wondering whether it needs to be passed by reference or not.

When you write a high-level Rust wrapper:

  • Being incompatible with raw C calls may be an important benefit. Higher-level wrapper will usually try to provide extra safety on top of the C API. Direct calls to the C API that bypass the wrapper could bypass the safety (ignore borrowing, or set the C library to a state that the Rust side didn't expect).

  • If you're building a higher-level wrapper you may need to add extra private fields for Rust's use, but if you promise your handle is just usize then you can't.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.