rust-from-c
Demonstrates compiling a Rust program and running it from a C program. Thank you to @kvngsley019 via DuckDuckGo for the tips.
This works on Linux using gcc. There was exactly zero fuss. However, it did not work on MacOS using either clang or gcc (via homebrew), or on Windows using icx. They each had various issues related to their own platform…
Anyways. First we will compile the code as a dynamic shared library; then we will compile it as static.
Write the code
In the project folder there are 4 code files that matter (plus the library that is created later):
./
Cargo.toml
src/
lib.rs
main.c
rust.h
Cargo.toml:
[package]
name = "rust-from-c"
version = "0.1.0"
edition = "2024"
[dependencies]
[lib]
crate-type = ["cdylib"]
lib.rs:
#[unsafe(no_mangle)]
pub extern "C" fn rtest(x: f64) -> f64 {
println!("Hello from Rust!");
x * 2.0
}
main.c:
#include "rust.h"
#include <stdio.h>
int main() {
printf("Hello from C!\n");
double x = 2.0;
printf("Your number is: %f\n", x);
double y = rtest(x);
printf("Now your number is: %f\n", y);
return 0;
}
rust.h:
double rtest(double x);
Compile the Rust code
From project root:
cargo build --release
This creates a .so file in target/release/. Rename this to rust.so (very creative - wonder if cargo can do that…turns out it can). Move the file to src/:
mv target/release/librust_from_c.so src/librust.so
Then compile:
gcc main.c -o main.o -L./ rust.so -Wl,-rpath=.
This creates a dynamic link, so both the main.o and the rust.so file must be in the same folder at runtime and the program must be executed from that directory. Modify rpath=. to rpath=src/ (for example) if that is not the intended behavior.
Then, running the program should give:
~/src$ ./main.o
Hello from C!
Your number is: 2.000000
Hello from Rust!
Now your number is: 4.000000
To inspect the .so file, issue either of these two commands:
nm -D --defined-only rust.so
objdump -T rust.so
Compile as static
Modify the entry in Cargo.toml to read:
[lib]
crate-type = ["staticlib"]
Then recompile, move the file to src/ like before. The gcc command is simpler now:
gcc main.c -o main.o -L./ librust.a
And now the file can be called from project root:
$ ./src/main.o
Hello from C!
Your number is: 2.000000
Hello from Rust!
Now your number is: 4.000000
Summary
All nicely wrapped up in a Makefile for convenience; run make static or make dynamic from the project root and it’ll work automagically!
static:
# Make the crate-type static
sed -i '/^\[lib\]/ { n; s/.*/crate-type = ["staticlib"]/ }' Cargo.toml
# Delete any .a file if it already exists
if ls src/librust.a; then rm src/librust.a; fi
# Compile and move the file
cargo build --release
mv target/release/librust.a src/librust.a
# Compile C, executable ends up in project directory
gcc src/main.c -o main.o src/librust.a
./main.o
dynamic:
# Make the crate-type dynamic
sed -i '/^\[lib\]/ { n; s/.*/crate-type = ["cdylib"]/ }' Cargo.toml
# Delete any .so file if it already exists
if ls src/librust.so; then rm src/librust.so; fi
# Compile and move the file
cargo build --release
mv target/release/librust.so src/librust.so
# Compile C, executable ends up in project directory but the .so
# remains in the src/ folder
gcc src/main.c -o main.o -L./ src/librust.so -Wl,-rpath=src/
./main.o