Leaking C types in Rust interfaces


#1

Let’s assume that we have a C interface we want to wrap for Rust

long cfunction();

the direct equivalent of this would be

use libc::c_long;
fn cfunction() -> c_long

Still, this leaks the c_long type to the crate user and will require either a cast on his side or importing the c_long type.
We could write

fn cfunction() -> i64

but the type will be 32-bit on a 32-bit Linux. We could use

fn cfunction() -> isize()

But then Windows will return a 32-bit integer and we will return a 64-bit one.

What is the recommended way to cope with those non-fixed-precision C types when writing wrappers for C functions?


#2

I don’t have that much experience wrapping c libs, so there might be some corner cases I’m not seeing, but I would quarantine that kind of behaviour as much as possible. So, expose rust types of fixed width and cast it (maybe with range checking) to the c type behind the wrapper.

As a rust programmer and potential user of a lib that uses a c lib behind the scenes, I don’t want to think about platform dependent behaviour of C types.


#3

BTW, there’s std::os::raw::c_long, so you don’t need to use libc just for the types.

From my experience with wrapping C code it’s good to split it into two layers:

  • Pure FFI expressing exactly what the C code does, using C types.
  • Higher-level Rust wrapper for it translating it to the way Rust works.

So if the result is an offset in an array, then Rust’s type for it is isize and I’d do:

fn rust_function() -> isize {
  unsafe { ffi::cfunction() as isize }
}

If the maximum value returned by the C function is known, then I’d use specific type size in Rust like i32. I’d add an assert!() or debug_assert!() verifying that assumption.


#4

Allow me to add that the “pure FFI” is usually implemented as a separate crate, usually named “<wrapped-lib>-sys”.
The rustic wrapper around this is then named just “<wrapped-lib>”.

Examples:


#5

This is correct. Adding on: most users would only use the Rustic library. The pure FFI library exists as an escape hatch in the case that some pattern is not provided for in the Rustic library.


#6

This is usually why interface libraries often come with both a low-level, bare-bones layer that exposes c_long for what it is, and a high-level interface that just disguises them as i64 for convenience.