cross compiling and statically linking against Rust libraries

Crustaceans are adaptable and can be found in all kinds of environments.

At CSIS we have traditionally written our back-end in Python, together with some C/C++ code for our Incident Response Toolkit.

A few years ago, mainly due to performance reasons, we started rewriting specific back-end services from Python to Rust, with great success. Now, for the sake of ease of development and testing, we are exploring the idea of moving parts of our C/C++ code base to Rust, too.

In order to do so, instead of re-writing everything in one swoop, we decided to try integrating Rust into our existing code base.

Following is a summary of our experiments, and a skeleton for writing a Rust library and calling it from a C/C++ application.


In order to be able to cross compile code, we need to first make sure that we have the desired targets installed. A list of available targets can be obtained via rustc --print target-list and new ones can be installed via rustup target add <target>.

Installed targets can be shown with rustup show:

$ rustup show
Default host: x86_64-unknown-linux-gnu

installed toolchains

stable-x86_64-unknown-linux-gnu (default)

installed targets for active toolchain


active toolchain

stable-x86_64-unknown-linux-gnu (default)
rustc 1.31.0 (abe02cefd 2018-12-04)

Project Setup

Once the targets have been properly setup we need to setup our project. Starting with Cargo.toml :

name = "mylib"
version = "0.1.0"
authors = ["john doe<>"]

libc = "*"

crate-type = ["staticlib"]

Now the source code for our library src/

extern crate libc;
use libc::uint32_t;

pub extern "C" fn addition(a: uint32_t, b: uint32_t) -> uint32_t {
a + b

And the code for our application caller.cpp:

#include <stdio.h>
#include <stdint.h>

extern "C" {
addition(uint32_t, uint32_t);

int main(void) {
uint32_t sum = addition(1, 2);
printf("%u\n", sum);

If this were a C application, only the extern "C" needed to be removed.

Next we need to indicate our desired target, which is done in .cargo/config, for example:


Linux binaries

In order to statically link Linux binaries the target needs to be set to -linux-musl.

  • .cargo/config

Alternatively we could choose x86_64-unknown-linux-musl for 64 bit binaries.

  • build
$ g++ -m32 -c -o linux_cpp_caller.o caller.cpp
$ g++ -static -m32 linux_cpp_caller.o -o linux_cpp_caller \
-L./target/i686-unknown-linux-musl/debug/ -lmylib

If we were targeting 64 bit, the -m32 option needed to be removed and the -L flag adjusted accordingly.

Windows Binaries

In order to statically link Windows binaries the target needs to be set to -pc-windows-gnu . In the case of 32 bit binaries special care is needed with regards to the exception handling model. A good summary can be found on the “rust-lang” Github repository and on stack overflow.

  • .cargo/config
rustflags = "-C panic=abort"

Alternatively we could choose x86_64-pc-windows-gnu for 64 bit binaries. If this were the case, the rustflags option could be removed.

  • build
$ i686-w64-mingw32-g++ -c -o win_cpp_caller.o caller.cpp
$ i686-w64-mingw32-g++ -static win_cpp_caller.o -o win_cpp_caller \
-L./target/i686-pc-windows-gnu/debug/ \
-lmylib \
-ladvapi32 \
-lws2_32 \

If the target was a 64bit environment, x86_64-w64-mingw32-g++ should be used instead and the -L flag adjusted accordingly.


With this post we hope to have provided a simple, working example of how Rust and C/C++ can inter-operate, which can be used as a starting point.

We would like to thank the contributors of the resources linked below.