Calling C from Rust
Today, let’s look at how to call C functions from Rust.
Suppose we have a library written in C, say
// /project/root/mathlib.h
double add(double x, double y);
// /project/root/mathlib.c
#include <math.h>
double add(double x, double y) {
return x + y;
}
To call the function add() from Rust, we import the external function with extern keyword
// /project/root/src/main.rs
unsafe extern "C" {
fn add(x: f64, y: f64) -> f64;
}
fn main() {
unsafe {
let result = add(2.0, 3.0);
println!("The result of addition is: {}", result);
}
}
Static Linking
First, let’s link it statically. That is, our Rust executable will embed the C library code in itself.
# we assume we are at /project/root/ directory
# create a static object file
gcc -fPIC -c -o libmathlib.o mathlib.c
# create an archive
ar rucs libmathlib.a libmathlib.o
Next, we need to create build.rs file that will link the C object file into the Rust executable
// /project/root/build.rs
use std::env;
fn main() {
let pwd = env::var("PWD").unwrap();
println!("cargo::rustc-link-search={}", pwd);
println!("cargo::rustc-link-lib=mathlib");
println!("cargo::rerun-if-changed=mathlib.c");
}
Now, we are ready to build and run!
# build
cargo build
# run
target/debug/calling-c-from-rust
The result of addition is: 5
We can verify that the libmathlib.a is statically linked
# libmathlib.a is not found in the dynamically linked dependencies
ldd target/debug/calling-c-from-rust
linux-vdso.so.1 (0x0000ffffb434c000)
libgcc_s.so.1 => /lib/aarch64-linux-gnu/libgcc_s.so.1 (0x0000ffffb4250000)
libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x0000ffffb4090000)
/lib/ld-linux-aarch64.so.1 (0x0000ffffb4310000)
Dynamic Linking
Next up, let’s see what changes if we want to dynamically link. The only change is that we need to compile into a shared library rather than static one.
gcc -shared -fPIC mathlib.c -o libmathlib.so
Note that we don’t need to invoke ar this time. The rest is exactly the same. Let’s build it again
# build
cargo build
# run
target/debug/calling-c-from-rust
The result of addition is: 5
Now, let’s make sure it is dynamically linked
# we see libmathlib.so
ldd target/debug/calling-c-from-rust
linux-vdso.so.1 (0x0000ffff93eec000)
libmathlib.so => /root/rust-and-c/libmathlib.so (0x0000ffff93e00000)
libgcc_s.so.1 => /lib/aarch64-linux-gnu/libgcc_s.so.1 (0x0000ffff93dc0000)
libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x0000ffff93c00000)
/lib/ld-linux-aarch64.so.1 (0x0000ffff93eb0000)
As expected, we do see libmathlib.so file appear.
Below is the full script that reproduces the steps here.
# clean up from previous run if any
mkdir -p calling-c-from-rust
rm -rf calling-c-from-rust
# create a new rust project from scratch
cargo new calling-c-from-rust
cd calling-c-from-rust
# write C header and source files
cat << EOF > mathlib.h
double add(double x, double y);
EOF
cat << EOF > mathlib.c
#include <math.h>
double add(double x, double y) {
return x + y;
}
EOF
# write rust program that calls add() function
cat << EOF > src/main.rs
unsafe extern "C" {
fn add(x: f64, y: f64) -> f64;
}
fn main() {
unsafe {
let result = add(2.0, 3.0);
println!("The result of addition is: {}", result);
}
}
EOF
# static linking
gcc -fPIC -c -o libmathlib.o mathlib.c
# linux
ar rucs libmathlib.a libmathlib.o
# macos (uncomment below)
# libtool -static -o libmathlib.a mathlib.o
# dynamic linking (uncomment below)
# gcc -fPIC -shared -o libmathlib.so mathlib.c
cat << EOF > build.rs
use std::env;
fn main() {
let pwd = env::var("PWD").unwrap();
println!("cargo::rustc-link-search={}", pwd);
println!("cargo::rustc-link-lib=mathlib");
println!("cargo::rerun-if-changed=mathlib.c");
}
EOF
cargo run