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