Overview
5 min read
Safe Rust proves your program free of use-after-free, data races, and out-of-bounds access before it ever runs. A handful of low-level operations — talking to C, dereferencing raw pointers, certain hardware-level tricks — cannot be proven safe by the compiler, so Rust gates them behind the unsafe keyword. This section builds the correct mental model (unsafe is a promise you make, not the any of Rust), then walks the full Foreign Function Interface toolchain: calling C from Rust, auto-generating bindings, and shipping Rust as a Node.js native addon to replace the kind of native modules you would otherwise write in C++.
The current stable toolchain is Rust 1.96.0 on the latest stable edition (2024); cargo new selects it automatically. The 2024 edition changes a few things you will see throughout: extern "C" blocks are written unsafe extern "C", an unsafe fn body is “safe by default” (unsafe_op_in_unsafe_fn), and you reach a static mut through &raw mut rather than &mut. Ecosystem examples are compile-verified against current releases (bindgen 0.72, napi/napi-derive 3, neon 1).
What You’ll Learn
Section titled “What You’ll Learn”- What
unsafeactually does — the five superpowers it unlocks — and the crucial ways it differs from TypeScript’sany - Why the borrow checker, type checker, and lifetimes stay fully on inside an
unsafeblock, and what undefined behavior is - How to write
unsafeblocks andunsafe fns, the// SAFETY://// # Safetyconventions, and the dangers ofstatic mut - How raw pointers (
*const T/*mut T) differ from references, and the rules for creating and dereferencing them - How the C ABI works in Rust:
extern "C",#[no_mangle],#[repr(C)], and what crosses the boundary safely - How to call real C code from Rust, link a C library, and marshal strings and structs across the FFI boundary
- How to auto-generate bindings from C headers with bindgen instead of hand-writing
externblocks - How to ship Rust as a Node.js native addon with napi-rs and the alternative Neon, replacing C++ addons
- How to wrap a small, audited
unsafecore behind a fully safe API — the “unsafe inside, safe outside” pattern - How to judge when
unsafe/FFI is genuinely necessary, and the many times a safe restructuring is the better answer
Topics
Section titled “Topics”| Topic | Description |
|---|---|
What unsafe Really Means (and What It Does Not) | The five superpowers, why unsafe is not TypeScript’s any, and what undefined behavior is. |
| Unsafe Blocks and Operations | Writing unsafe blocks, calling unsafe fns, static mut, and the 2024-edition unsafe_op_in_unsafe_fn rule. |
Raw Pointers: *const T and *mut T | How raw pointers differ from references, creating them in safe code, and dereferencing them under unsafe. |
FFI Basics: extern "C", #[no_mangle], #[repr(C)], and the C ABI | The machinery that lets Rust speak the C ABI and present a stable, predictable memory layout. |
| Calling C Code from Rust | Linking a C library with build.rs + cc, declaring its functions, and marshaling strings (CStr/CString) and structs across the boundary. |
| Generating Bindings with bindgen | Auto-generating extern declarations from C headers with a build.rs instead of writing them by hand. |
| Node.js Native Addons with napi-rs | Shipping Rust as an npm-installable native addon with #[napi] exports, building it, and calling it from Node. |
| Node.js Native Addons with Neon | The Neon alternative for native Node.js addons, and how it compares to napi-rs. |
Building Safe Abstractions Over unsafe | The “unsafe inside, safe outside” pattern: upholding invariants so callers never write unsafe. |
When unsafe and FFI Are Actually Necessary | A decision framework — and the many times a safe restructuring beats reaching for unsafe. |
Learning Objectives
Section titled “Learning Objectives”By the end of this section, you will be able to:
- Explain precisely what
unsafeunlocks and what it leaves unchanged, and refute the “unsafeis Rust’sany” misconception - Write and review
unsafeblocks andunsafe fns with the// SAFETY:and/// # Safetyconventions the ecosystem expects - Create and dereference raw pointers correctly, and articulate the validity invariant every dereference depends on
- Declare and call C functions across the FFI boundary using
extern "C",#[no_mangle], and#[repr(C)] - Link a native C library into a Rust crate and pass strings and structs back and forth without leaking or corrupting memory
- Generate bindings from a C header with bindgen and a
build.rs, instead of hand-maintainingexterndeclarations - Build a Node.js native addon in Rust with napi-rs (or Neon) and consume it from JavaScript
- Encapsulate an
unsafecore behind a safe API, and run your code under Miri to catch undefined behavior - Decide whether a given problem truly needs
unsafe/FFI, and justify reaching for a safe alternative when it does not
Prerequisites
Section titled “Prerequisites”- Section 05: Ownership —
unsafeadds five abilities but keeps ownership, borrowing, and lifetimes fully enforced; you must be fluent in them to reason about soundness. - Section 10: Smart Pointers — many safe abstractions over
unsafe(and the alternatives to it) are built fromBox,Rc/RefCell, and friends. - Section 19: WebAssembly — the “talk to another world” mindset and the
cdylib/externmachinery there carry directly into native FFI.
A working knowledge of error handling (Result at the FFI boundary) and modules and packages (build.rs, linking, crate types) will also help.
Estimated Time
Section titled “Estimated Time”Approximately 12 hours, including reading, hands-on practice, and the per-topic exercises.
Continue to Section 21: Performance, where profiling, benchmarking, and memory-layout work occasionally make a small, justified unsafe block earn its keep — once you have the discipline this section teaches.