Unsafe Rust does not turn off the rest of the language. It permits a small set of operations whose correctness depends on invariants the compiler cannot prove. Foreign-function interfaces add another boundary: the Rust and foreign sides must agree about layout, ownership, lifetimes, nullability, panic behavior, and the calling convention.
Learning outcomes
- decide whether an unsafe operation is necessary;
- isolate unsafe code behind a narrow safe interface;
- use CString and CStr for C-compatible text boundaries;
- document and test pointer, layout, lifetime, and error invariants;
- identify why an example is not automatically production-ready.
Prerequisites
Complete the ownership and borrowing guide. You should also be comfortable with Cargo builds, tests, and reading compiler diagnostics. Do not run FFI examples against an untrusted native library.
What unsafe permits
Rust’s unsafe features include dereferencing raw pointers, calling unsafe
functions, accessing mutable statics, using unions, and implementing unsafe
traits. The unsafe keyword does not prove that an operation is correct; it
marks a review boundary where the programmer must uphold the contract.
Keep the unsafe block small
use std::slice;
/// # Safety
/// `ptr` must be non-null, valid for reads of `len` bytes, and point to one
/// allocated object for the duration of this call.
unsafe fn view_bytes<'a>(ptr: *const u8, len: usize) -> &'a [u8] {
// SAFETY: The caller documents and upholds the pointer and lifetime contract.
unsafe { slice::from_raw_parts(ptr, len) }
}
This function is intentionally unsafe because the caller supplies facts Rust cannot verify. A safer wrapper should validate what it can, avoid returning a reference beyond the owner’s lifetime, and keep the unsafe operation in one reviewable location.
C-compatible strings
CString owns a nul-terminated string without interior nul bytes. CStr views
a nul-terminated string borrowed from a foreign boundary.
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
fn outbound(value: &str) -> Result<CString, std::ffi::NulError> {
CString::new(value)
}
/// # Safety
/// `ptr` must be null or point to a valid, live, nul-terminated C string for
/// the entire lifetime `'a` of the returned borrow.
unsafe fn inbound<'a>(ptr: *const c_char) -> Option<&'a CStr> {
if ptr.is_null() {
return None;
}
// SAFETY: The foreign function contract must guarantee a valid nul-terminated string.
Some(unsafe { CStr::from_ptr(ptr) })
}
The inbound function still has an unsafe contract: non-null is not enough. The pointer must be valid and properly terminated, and the returned borrow must not outlive the foreign allocation.
FFI design checklist

| Boundary question | Required decision |
|---|---|
| Calling convention | Match the declared ABI, commonly extern "C" |
| Layout | Use FFI-safe representations and verify alignment and sizes |
| Ownership | State who allocates, borrows, and frees every pointer |
| Nullability | Model nullable pointers explicitly and check them |
| Strings | Specify encoding and termination; reject invalid input |
| Errors | Do not let unwinding cross an unsupported foreign boundary |
| Threads | Document Send/Sync and thread-affinity assumptions |
| Versioning | Test the exact native library and supported ABI versions |
Avoid passing Rust references, trait objects, or unspecified layouts across a C ABI. Prefer opaque handles and explicit lengths when the foreign contract needs them.
Safe wrappers are contracts
A wrapper is sound only if every safe caller receives the guarantees promised by its API. Validate lengths before pointer arithmetic, reject invalid encodings, preserve ownership rules, and convert foreign errors into a bounded Rust error. Unit tests should cover null pointers, empty input, maximum lengths, malformed text, foreign failure codes, and cleanup on every path.
WebAssembly and other targets
WebAssembly bindings add another ABI and memory model. Verify the chosen tool’s
current generated interface, supported types, ownership convention, and panic
behavior. Do not infer browser security, sandbox isolation, or portability from
the presence of a .wasm file.
Common mistakes and troubleshooting
- One large unsafe block: split it into operations with named invariants.
- from_raw without an ownership plan: document exactly who will reclaim the allocation and when.
- Pointer plus length overflow: validate the range before constructing a slice.
- Rust panic across FFI: catch or prevent unwinding according to the ABI and library contract.
- Layout guessed from a compiler: use explicit representations and verify the foreign declaration.
- Safe wrapper with unchecked assumptions: make the assumptions part of the function contract and test them.
Security and production considerations
Treat every foreign call as an untrusted boundary. Review memory validity, integer conversion, resource exhaustion, thread safety, error handling, symbol resolution, supply-chain provenance, and release reproducibility. Fuzz parsers and boundary inputs where practical. This guide does not certify any FFI code as safe or suitable for production.
Summary
Use unsafe only where necessary, isolate it, document every invariant, and test
the foreign contract independently. CString and CStr help with C strings,
but they do not solve pointer validity, lifetime, ABI, or ownership by
themselves.
Next step and technical review flags
Revisit Understanding Ownership and Borrowing before
attempting an implementation. Verify each statement and example against the
current Rust Reference, Rustonomicon, std::ffi documentation, platform ABI
documentation, and the selected WebAssembly toolchain. Independent unsafe-code
review is mandatory before approval.
Join the discussion
0 CommentsAsk questions, add context, or share what worked.
More from Melochip Editorial
Learn what Playwright testing is, why modern QA teams use it, how testers can get started, and the major types...
7 min read · Sep 19, 2026
Learn how Kubernetes Deployments and ReplicaSets work with practical YAML, scaling, self-healing, rolling upda...
7 min read · Sep 17, 2026
Learn Kubernetes security best practices with RBAC, Secrets, ServiceAccounts, Pod securityContext, NetworkPoli...
6 min read · Sep 17, 2026Related articles
Prepare for Docker interviews with practical questions and real-time scenarios covering images, containers, Do...
2 min read · Sep 17, 2026
Learn what Playwright testing is, why modern QA teams use it, how testers can get started, and the major types...
7 min read · Sep 19, 2026
Learn how Kubernetes Deployments and ReplicaSets work with practical YAML, scaling, self-healing, rolling upda...
7 min read · Sep 17, 2026
Troubleshoot Kubernetes issues including Pending Pods, CrashLoopBackOff, ImagePullBackOff, service routing, in...
7 min read · Sep 17, 2026Stay in the loop. Get new Melochip articles
Receive Melochip article updates when new articles are available. Explicit opt-in only.
No comments yet. Start the discussion.