FFI & C Interop
Rust's Foreign Function Interface lets you reuse decades of battle-tested C libraries and expose fast Rust code to other languages — all through the shared C ABI.
Learn FFI & C Interop in our free Rust course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Rust course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll call C from Rust, export Rust functions to C, convert strings across the boundary, and share C-compatible structs — learning where unsafe is required and why.
What You'll Learn in This Lesson
1️⃣ Calling C from Rust
An extern "C" block declares foreign functions using the C calling convention. Their bodies live in a C library you link against. Because Rust can't verify foreign code, every call must sit inside an unsafe block.
The declared signature must match the C function exactly — a mismatch is undefined behaviour, not a compile error, which is part of why this is unsafe .
2️⃣ Calling Rust from C
To make a Rust function callable from C, give it the C ABI with extern "C" and keep its symbol name with #[no_mangle] so the linker can find it under the expected name.
Without #[no_mangle] , Rust mangles the symbol and C could never link to add by name.
3️⃣ Strings and C-Compatible Structs
Rust strings are UTF-8 and not NUL-terminated; C strings are. Convert with CString (owned, to send) and CStr (borrowed, to receive). Whoever allocates a buffer must free it.
For structs, add #[repr(C)] so Rust lays out the fields exactly as C expects. The libc crate supplies the C types, and bindgen can generate these declarations from a header.
Write a C-callable function and the C declaration that would invoke it.
📋 Quick Reference — FFI
Practice quiz
What does FFI stand for?
- Foreign Function Interface
- Fast Function Inlining
- File Format Interop
- Free Form Input
Answer: Foreign Function Interface. FFI, the Foreign Function Interface, lets Rust call and be called by other languages, usually C.
What does an extern "C" block declare?
- Inline assembly
- A new Rust module
- Functions using the C calling convention / ABI
- A C compiler
Answer: Functions using the C calling convention / ABI. extern "C" tells Rust to use the C ABI for the listed functions.
Why is #[no_mangle] used on a Rust function exported to C?
- To mark it unsafe
- To keep its symbol name unchanged so C can link to it
- To make it faster
- To inline it
Answer: To keep its symbol name unchanged so C can link to it. #[no_mangle] disables name mangling so the symbol matches what C expects.
Why must calls to foreign C functions be inside unsafe?
- unsafe makes it faster
- It is only a style choice
- C is slow
- The compiler cannot verify the foreign code upholds Rust's guarantees
Answer: The compiler cannot verify the foreign code upholds Rust's guarantees. Rust cannot check the safety of foreign code, so you take responsibility with unsafe.
Which type represents an owned, NUL-terminated C string in Rust?
- CString
- String
- OsString
- Vec<char>
Answer: CString. std::ffi::CString owns a NUL-terminated byte buffer suitable for C.
Which type borrows a NUL-terminated C string (a *const c_char)?
- str
- CStr
- CString
- String
Answer: CStr. std::ffi::CStr is the borrowed view over an existing C string.
What does #[repr(C)] on a struct guarantee?
- It cannot be mutated
- It is copied
- It is faster
- C-compatible field layout and ordering
Answer: C-compatible field layout and ordering. #[repr(C)] gives the struct the same memory layout C uses, so it can cross the boundary.
What is the libc crate typically used for?
- Garbage collection
- Async runtimes
- Bindings to standard C library types and functions
- Web servers
Answer: Bindings to standard C library types and functions. libc provides Rust bindings to platform C library types and functions like c_int and malloc.
What does the bindgen tool do?
- Compiles Rust to C
- Generates Rust FFI bindings from C headers
- Runs benchmarks
- Formats code
Answer: Generates Rust FFI bindings from C headers. bindgen reads C header files and generates the corresponding Rust extern declarations.
When passing a Rust pointer across FFI, which is true?
- Raw pointers carry no lifetime or aliasing guarantees, so you uphold them manually
- Rust frees it automatically in C
- The borrow checker tracks it across the boundary
- Lifetimes are enforced in C
Answer: Raw pointers carry no lifetime or aliasing guarantees, so you uphold them manually. Raw pointers drop Rust's safety guarantees; correct use across FFI is the programmer's responsibility.