Linking Static Linux Libraries in Rust

I had a hell of a time getting this to work. Here’s how I did it. Let’s write a small library in C++.

// mysum.h
#ifdef __cplusplus
// extern "C" is needed so that C++ doesn't mangle the mysum symbol into something like _Z5mysumii
extern "C"
{
#endif

int mysum(int i, int j);

#ifdef __cplusplus
}
#endif
// mysum.cpp
include "mysum.h"

int mysum(int i, int j)
{
    return i + j;
{

In Linux, we can compile them with g++ into an object file then package them in a static library with ar.

g++ -c mysum.cpp -o mysum.o
ar rcs mysum.a mysum.o

Create a rust project named libtest.

cargo init libtest

Create a directory libs inside libtest, and copy the mysum.a file as libmysum.a into libtest. The header doesn’t need to be included for this example.

I’m going to repeat that: mysum.a needs to be named libmysum.a inside libtest/libs.

Inside libtest, create a build.rs file and edit it to look like this:

fn main() {
    println!("cargo:rustc-link-lib=static=stdc++");
    println!("cargo:rustc-link-lib=mysum");
    println!("cargo:rustc-link-search=native=./libs");
}

Notice that cargo:rustc-link-lib=mysum lacks the lib prefix? It’s strange.

Add a line to the [package] section in Cargo.toml file to include the line build = “build.rs”

[package]
name = "libtest"
version = "0.1.0"
edition = "2021"
build = "build.rs"

Finally, it’s time to edit src/main.rs:

#[link(name="mysum")]
extern "C" {
    fn mysum(i: i32, j: i32) -> i32;
}

fn main() {
    println!("Hello, world!");
    let result = unsafe {
        mysum(5, 6)
    };
    println!("Result: {}", result);
}

Notice, again, that the library is named libmysum.a, but it’s referred to as just mysum. Very strange, indeed.

cargo run
...
Hello, world!
Result: 11

Okay. It’s time for some THAT WAY MADNESS LIES.

Earlier, we were careful to wrap all of our C++ functions inside an extern “C” block. This limits us. Maybe we have a static library built for C++ which cannot be altered. Maybe it has overloaded functions. Who knows? Let’s imagine that we have this kind of a library.

// multisum.h
int multisum(int i, int j);

int multisum(int i, int j, int k);
// multisum.cpp
// notice no extern "C" block
#include "multisum.h"

int multisum(int i, int j)
{
    return i + j;
}

int multisum(int i, int j, int k)
{
    return i + j + k;
}

Let’s compile it into an object file and pack it as above. I’ll pretend that it’s already inside libtest/libs as libmultisum.a, but what will we do about the function overloading? Rust doesn’t let you do that! Behold, and despair!

nm libmultisum.a

multisum.o:
0000000000000000 T _Z8multisumii
0000000000000018 T _Z8multisumiii

You better believe that those are the symbol names for the functions. Let’s get all of our ducks in a row. build.rs:

fn main() {
    println!("cargo:rustc-link-lib=static=stdc++");
    println!("cargo:rustc-link-lib=multisum");
    println!("cargo:rustc-link-search=native=./libs");
}

src/main.rs:

#[link(name="multisum")]
extern "C" {
    fn _Z8multisumii(i: i32, j: i32) -> i32;
    fn _Z8multisumiii(i: i32, j: i32, k: i32) -> i32;
}

fn main() {
    println!("Hello, world!");
    let result1 = unsafe {
        _Z8multisumii(5, 6)
    };
    let result2 = unsafe {
        _Z8multisumiii(5, 6, 7)
    };
    println!("2-number result: {}", result1);
    println!("3-number result: {}", result2);
}
cargo run
...
Hello, world!
2-number result: 11
3-number result: 18