C interface in Rust crate: bloat in staticlib object

I have a Rust crate and I want to expose a C interface for calling into it. For simplicity let's say I have a Rust API with a generic type with some associated functions:

struct Foo<T> (T);

impl<T: Copy> Foo<T> {
  fn get(&self) -> T { self.0 }
  // ...
}

and I want to expose a C API and an object file sort of like

typedef struct (int16_t x) foo16_t;
typedef struct (int32_t x) foo32_t;
typedef struct (int64_t x) foo64_t;

int16_t foo16_get(const foo16_t* foo);
int32_t foo32_get(const foo32_t* foo);
int64_t foo64_get(const foo64_t* foo);

and so on.


I'm unsure about the best practices to produce both a rust .rlib and an object file .a from the same crate. This is what I tried:

I wrote a mod c_api where I have

type foo16_t = Foo<i16>;
type foo32_t = Foo<i32>;
type foo64_t = Foo<i64>;

#[unsafe(no_mangle)]
pub extern "C" fn foo16_get(foo: &foo16_t) { foo.get() }

#[unsafe(no_mangle)]
pub extern "C" fn foo32_get(foo: &foo32_t) { foo.get() }

#[unsafe(no_mangle)]
pub extern "C" fn foo64_get(foo: &foo64_t) { foo.get() }

(The code duplication to instantiate everything twice is not good, but I guess I can fix it with macros and concat_ident). I passed cbindgen over this module to produce the .h header. I made the crate-type = ["lib", "staticlib"]. The problem is: this produces a static lib that's 7MB in size!

Looking with objdump, I see tons of functions that really shouldn't be there: stuff with vec, panic handlers for int overflow, etc. I should emphasize: I built with --release and the crate is no_std. I don't expect panic-on-overflow neither do I expect functions related to Vec.

Like I said, I don't know really know what are the best practices regarding organisation of code in a crate that produces both a Rust and a C interface. How can I ensure the staticlib object only contains a subset of functions, in my case those described in mod c_api and its callees? Split in two crates (the Rust crate with type lib and another crate-c-api with type staticlib that depends on the former)? I tried that and it still builds the same 7MB libcrate.a...

Thanks for any help! It's my first time interfacing with C.

Regarding some of the „unnecessary“ symbols: overflow checks are standard in Rust, even in release mode, you can turn them off by via the ´overflow-checks´ flag in your Cargo.toml file.

Not quite.

If not specified, overflow checks are enabled if debug-assertions are enabled, disabled otherwise.

1 Like

Regardless of your issue with your staticlib, for the benefit of your lib/.rlib users, you should do this crate split (or, if you prefer, put the C interface functions behind a feature). To the best of my understanding, having no_mangle symbols in a Rust library means they will make it into the final binary regardless of whether they’re used, and if the build has multiple versions of your library, this could cause UB (or linker errors?).

2 Likes

weird...I seriously thought that's the case

Ahh that makes sense. Thanks for the tip!