Calling C code from Rust falls under FFI (Foreign Function Interface) and is unsafe by nature, as it's crossing Rust boundary and all the checks that comes with it.
The syntax itself is pretty simple, so create a project and:
// src/main.rs
extern "C" {
fn print_num(num: i32);
}
fn main() {
println!("[rust] start");
unsafe { print_num(50) }
}
We are defining an external function which uses C ABI convention - which has got to do with the way stack and registers are arranged, when making a call to another function.
And for our C library:
// src/num.c
#include <stdio.h>
void print_num(int num) {
printf("[ c ] num is %d", num);
}
But if we cargo run
here, we'd get main.rs:8: undefined reference to print_num
, meaning compiler is not finding our C function so we need to link the library:
$ # in root of project
$ cc -c src/num.c
$ cc -shared num.o -o libnum.so
First cc
would generate lib.o
but rustc
needs a shared obejct, which is generated from that and starts its name with lib
. Now we can link it to our Rust code (omit the lib
at start):
$ rustc -l num -L . src/main.rs
Which looks for (lib)num.so
in current .
path. This should compile and give you the main
binary, which can't be executed:
$ ./main
./main: error while loading shared libraries: libnum.so: cannot open shared object file: No such file or directory
$ ldd main
…
libnum.so => not found
Because you've used a dynamic library, and ldd
can't find it, you can't run your binary. You can either install libnum.so
in appropriate path or use a static library:
$ ar rcs libnum.a num.o
$ rustc -l static=num -L. src/main.rs
$ ./main
[rust] start
[ c ] num is 50
Perfect! Let's simplify our workflow, create build.rs
in root of project:
fn main() {
println!("cargo:rustc-link-search=.");
println!("cargo:rustc-link-lib=static=num");
}
And now we can use cargo run
and cargo build
the usual way. We can also declare the name of library where it's being used so instead of println!("cargo:rustc-link-lib=static=num");
in build.rs
:
// src/main.rs
#[link(name = "num", kind = "static")]
extern "C" {
fn print_num(num: i32);
}
which links libnum.a
and if we remove the kind
part defaults to dynamic versin libnum.so
.
Finally if you're not interested in handling the compilation of library files yourself, there's cc
crate which takes care of that for you:
// Cargo.toml
…
[build-dependencies]
cc = "1.0"
And your build.rs
:
// build.rs
fn main() {
cc::Build::new()
.file("src/num.c")
.compile("anything");
}
And that's all!
Top comments (0)