C.W.K.
Stream
Lesson 02 of 05 · published

FFI — Talking to C and the Platform

~11 min · epilogue, ffi, extern, objc2

Level 0Rust Curious
0 XP0/80 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete

The most common reason to use unsafe is FFI — the Foreign Function Interface, how Rust talks to C libraries and operating-system APIs. It's also how Rust slots into existing codebases written in other languages.

Calling C from Rust

An extern "C" block declares functions from a C library; calling them is unsafe because the compiler can't verify the foreign code upholds Rust's guarantees. You declare the signature, link the library, and wrap the call — typically in a safe Rust function that validates inputs and outputs so callers never touch the raw FFI.

Why FFI matters

Decades of battle-tested C and system libraries exist; FFI lets Rust use them without rewriting. It also works the other way — Rust can expose a C-compatible API that C, Python, or any FFI-capable language calls. This two-way bridge is why Rust adopts incrementally: a single hot module rewritten in Rust can slot into a large C or Python codebase.

FFI is 'unsafe wrapped in safe,' at scale. A typical Rust binding to a C library has a thin unsafe extern layer at the bottom and a safe, idiomatic Rust API on top. You audit the unsafe boundary once; everyone using the crate stays in safe Rust. Crates like the objc2 family do exactly this for macOS system APIs.

The macOS example

A concrete case: on macOS, bringing a window to the front sometimes requires calling Cocoa's NSApplication.activate() — a system API with no pure-Rust equivalent. Crates in the objc2 family wrap that Objective-C FFI in a safe Rust interface. Cinder's native core uses exactly this pattern: a small, audited FFI layer to reach a macOS-only capability, exposed to the rest of the app as ordinary safe Rust. That's FFI doing its real job — reaching the platform without surrendering safety everywhere else.

Code

A C function behind a safe Rust wrapper·rust
// Declaring and calling a C function via FFI (calls are unsafe):
unsafe extern "C" {
    fn abs(input: i32) -> i32; // from the C standard library
}

// Wrap the unsafe call in a safe function — callers never see unsafe:
fn safe_abs(n: i32) -> i32 {
    unsafe { abs(n) } // we guarantee this call is sound
}

fn main() {
    println!("{}", safe_abs(-42)); // 42
    // Real FFI (e.g. objc2 wrapping Cocoa) follows this same shape:
    // a tiny unsafe boundary beneath a safe Rust surface.
}

External links

Exercise

Declare an unsafe extern "C" block for the C standard library's abs function and call it through a safe Rust wrapper. Then describe, conceptually, how a crate like objc2 applies this same pattern to wrap a whole platform API — what lives in the unsafe layer, and what callers see.
Hint
The unsafe layer is the raw extern declarations and the calls into the foreign code; callers see a safe, idiomatic Rust API on top. objc2 audits the Objective-C message-passing boundary once so app code can call macOS APIs without writing any unsafe itself.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.