Simple C library with Rust
Statically-linked
Create a simple C library, c-code/my-library.c
:
int square(int num) {
return num * num;
}
This step is not needed for static linking (because we’ll use the Rust toolchain to do it for us), but we could compile this to an object file:
$ cc my-library.c -c
$ ls
my-library.c my-library.o
$ file my-library.o
my-library.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
Write some Rust code that will make use of it. We have to tell Rust that there exists an external function named square
that takes and returns a signed 32-bit int:
#[link(name="mylib")]
extern "C" {
fn square(val: i32) -> i32;
}
fn main() {
let sq = unsafe { square(3) };
println!("3**2 = {}", sq);
}
Our function is considered unsafe
and must be wrapped as such. But in order to let Rust access this library, we need a build.rs
build script located at the root of our crate (not in the src/
folder):
fn main() {
// Recompile if my-library.c is updated
println!("cargo:rerun-if-changed=c-code/my-library.c");
// Use the `cc` crate to build a C file and statically link it.
cc::Build::new()
.file("c-code/my-library.c")
.compile("mylib");
}
Note that this build script bridges the name of our file (my-library.c
) and the library name (mylib
). Since this is using the cc
crate, we have to include it in the Cargo.toml
:
[build-dependencies]
cc = "1.0.46"
With this, we’re set:
$ cargo run
Compiling simple-ffi v0.1.0 (/mnt/c/Users/adam/c)
Finished dev [unoptimized + debuginfo] target(s) in 0.80s
Running `target/debug/c`
3**2 = 9
Dynamically-linked
We can also dynamically link to a shared object (.so
) file instead. The Cargo.toml
file no longer needs the [build-dependencies]
section.
The Rust binary no longer needs to specify that it’s linking to a library:
//#[link(name="mylib")] <-- this line not needed
extern "C" {
fn square(val: i32) -> i32;
}
fn main() {
let sq = unsafe { square(3) };
println!("3**2 = {}", sq);
}
To compile our program, rustc
will need to know about the library. Therefore, our build.rs
script specifies rustc-link-search
in order to know where the library is during compilation. The build script also specifies that we want to dynamically link to the dylib my-library
.
fn main() {
println!("cargo:rustc-link-search=native=c-code/"); // +
println!("cargo:rustc-link-lib=dylib=my-library");
}
NOTE that while we’re calling the library my-library
, the linker actually looks for a file with a ‘lib’ prefix, libmy-library.so
, so we have to compile it accordingly:
$ gcc c-code/my-library.c -shared -o libmy-library.so
$ ls *.so
libmy-library.so
$ file libmy-library.so
libmy-library.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=49953c0cb91c9212594e24e6e3c3ee531f06a9df, not stripped
We should be able to build now:
$ cargo build
Compiling simple-ffi v0.1.0 (/mnt/c/Users/adam/c)
Finished dev [unoptimized + debuginfo] target(s) in 0.67s
$ ./target/debug/simple-ffi
./target/debug/simple-ffi: error while loading shared libraries: libmy-library.so: cannot open shared object file: No such file or directory
$ ldd target/debug/simple-ffi
linux-vdso.so.1 (0x00007fffe5d32000)
libmy-library.so => not found
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f7c61a70000)
librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f7c61860000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f7c61640000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f7c61420000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f7c61020000)
/lib64/ld-linux-x86-64.so.2 (0x00007f7c62000000)
The binary file can’t find the library at runtime, and running ldd
shows that it’s attempting and failing to dynamically load the shared object file we compiled. Even if that file is in the same directory, it doesn’t work because that’s not how Linux loads its shared objects. You have to either copy the .so file into a folder specified by LD_LIBRARY_PATH
or update your LD_LIBRARY_PATH
when running. The quick fix is to do the latter:
$ LD_LIBRARY_PATH=. ./target/debug/simple-ffi
3**2 = 9
Additional details
If you compile with the -c
flag instead of -shared
, even with the build.rs
script and main.rs
setup for dynamic linking, it appears that the library will be statically linked. I haven’t dived into this much, but in writing this post, I ran into my hopefully-dynamically linked accidentally being statically linked.