Rust from C

24 Sep 2025

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