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.