Rust in Linux code - How does it work with C?

I recently came across a youtube video from recent KubeCon 2025 that talked about how kernel drivers and modules can be written in Rust now.
If you’re wondering how it stitches with C, read on. This post talks about object files, shared libraries that enable Rust coexist with C in Linux with examples.
Part 1: What Exactly Is an Object File?
Let’s start with the basics. When you compile a C file, like this:
// hello.c
#include <stdio.h>
void hello() {
printf("Hello from a .o file!\n");
}
> gcc -c hello.c -o hello.o
You don’t get an executable. You get an object file: hello.o
.
An object file is:
A partial chunk of machine code (not complete enough to run).
Contains code, data, and symbol information (like which functions are defined or still needed).
Relocatable code that can be shifted around in memory when linked.
> file hello.o
hello.o: ELF 64-bit LSB relocatable ...
# “Relocatable” means: not yet ready to run. It needs to be linked.
# nm will list symbols inside the object file. You’ll see something like:
> nm hello.o
00000000 T hello
# T means the symbol hello is defined in the text (code) section.
Part 2: Linking Object Files into an Executable
Object files by themselves are useless, but they become powerful when linked.
Create a main.c
that uses the hello
function:
// main.c
void hello();
int main() {
hello();
return 0;
}
Now, compile and link both:
> gcc -c main.c -o main.o
> gcc main.o hello.o -o hello_exec
> ./hello_exec
You just built an executable manually by compiling and linking .c
files into .o
, and then linking those into a final binary. That linking step is where the magic happens and it's done by the linker, ld
, often invoked by gcc
automatically.
Part 3: Shared Libraries (.so
) Explained
A shared library (like libc.so
) is a full-fledged library that multiple programs can dynamically link to at runtime. Think of it as a plugin where the program depends on it but doesn’t carry its code around.
Let’s build one from hello.c
:
# -fPIC tells the compiler to make the code position
# independent, which is necessary for shared libraries.
> gcc -fPIC -c hello.c -o hello.o
> gcc -shared hello.o -o libhello.so
Now build main.c
again, but link against the shared library:
> gcc -c main.c -o main.o
> gcc main.o -L. -lhello -o main_shared
> LD_LIBRARY_PATH=. ./main_shared
Hello from a .o file!
# dynamic linking in action — the function hello() lives in a separate
# file (libhello.so) and is loaded when the program runs.
> ldd main_shared
libhello.so => ./libhello.so
Part 4: Static Libraries (.a
) Explained
A static library is simply a collection of object files bundled together. Unlike shared libraries, static libraries get baked into the executable during compilation.
# Build a static library:
> ar rcs libhello.a hello.o
# Now link main.o with it:
> gcc main.o libhello.a -o main_static
> ./main_static
Hello from a .o file!
# output is same, but hello() is statically linked
# as shown by the output of this command
> ldd main_static
not a dynamic executable
Part 5: Modifying dynamic libraries
Linked libraries can be modified without making changes to the actual program or recompiling it again.
void hello() {
printf("MODIFIED HELLO\n");
}
Rebuild libhello.so
, but don’t touch main_shared:
> gcc -fPIC -c hello.c -o hello.o
> gcc -shared hello.o -o libhello.so
> LD_LIBRARY_PATH=. ./main_shared
MODIFIED HELLO
If you try the same with main_static
, nothing changes. You’d have to re-link it to get the update.
How Does This Relate to Rust in the Kernel?
The Linux kernel now supports writing modules and drivers in Rust. But the kernel is written in C. So following is how it works.
Rust code (
.rs
) is compiled byrustc
into.o
files.C code (
.c
) is compiled bygcc
into.o
files.Both are linked together by
ld
using kernel build scripts.
As long as both sides agree on calling conventions and symbol names, they can interoperate. Rust functions that need to be visible to C use #[no_mangle] extern "C"
, just like C functions can be declared in Rust via extern
.
Object files are the common language spoken by both Rust and C and the linker (ld
) is the translator.
So the next time you're building something and see .o
, .a
, or .so
, you’ll know exactly what’s happening under the hood.
Subscribe to my newsletter
Read articles from Harsha Gelivi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
