1. unsafe does not disable the borrow checker
An unsafe block permits a small set of additional operations, but most Rust rules still apply. The responsibility shifts to you to uphold the invariants the compiler cannot verify.
2. Raw pointers
fn main() { let mut value = 10; let read = &value as *const i32; let write = &mut value as *mut i32; unsafe { println!("{}", *read); *write = 20; } }Raw pointers may be null, dangling or unaligned. Dereferencing them requires unsafe.
3. Unsafe functions
unsafe fn read_address(ptr: *const u8) -> u8 { unsafe { *ptr } }Document the safety contract: what the caller must guarantee about validity, alignment, lifetime and aliasing.
4. Build safe wrappers
A strong pattern is a very small unsafe core surrounded by a safe API:
pub fn first_byte(data: &[u8]) -> Option<u8> { if data.is_empty() { None } else { unsafe { Some(*data.as_ptr()) } } }In this example the unsafe operation is unnecessary because safe indexing exists, but it demonstrates the boundary: validate invariants before entering unsafe code.
5. Unsafe traits
An unsafe trait has invariants that implementations must uphold. Marking an implementation unsafe means the implementer asserts those requirements are satisfied. This is advanced library-author territory.
6. Foreign Function Interface (FFI)
FFI allows Rust to call C-compatible libraries and allows external code to call Rust through a stable ABI boundary.
unsafe extern "C" { fn abs(input: i32) -> i32; } fn main() { let value = unsafe { abs(-10) }; println!("{value}"); }7. Export a Rust function to C
#[unsafe(no_mangle)] pub extern "C" fn add(a: i32, b: i32) -> i32 { a + b }Exact attributes and ABI requirements depend on the Rust edition/toolchain and target. Keep the exported surface small and test it from the consuming language.
8. FFI-safe data types
Do not pass arbitrary Rust types such as String or Vec directly through a C ABI. Use C-compatible primitives, explicit pointer/length pairs or #[repr(C)] structures designed for the boundary.
9. Ownership across FFI
Decide who allocates and who frees every resource. A common design exports paired create/free functions so memory is released by the same allocator/runtime that created it.
10. Validate external pointers
Foreign callers can give you null or invalid pointers. Raw-pointer validation is limited—checking for null does not prove memory is valid—so FFI APIs should minimize pointer complexity and document strict contracts.
11. Panic boundaries
Do not allow unwinding behavior to cross an ABI boundary that does not support it. Convert failures into explicit error codes/results appropriate for the foreign API.
12. WebAssembly overview
Rust can compile to WebAssembly targets, allowing selected Rust logic to run in browsers, server-side Wasm runtimes and other sandboxed environments.
rustup target add wasm32-unknown-unknown cargo build --target wasm32-unknown-unknown --release13. wasm-bindgen
For browser JavaScript interoperability, the wasm-bindgen ecosystem generates bindings that make it easier to expose Rust functions and work with JavaScript values.
14. Good Wasm candidates
- CPU-heavy parsing/transformations
- Portable domain logic
- Image/data processing
- Cryptographic operations through reviewed libraries
- Reusable logic shared across supported Wasm hosts
15. What not to assume about Wasm
WebAssembly does not automatically make an application faster. Boundary conversion, host APIs, payload copying and deployment constraints all matter. Benchmark the real workload.
16. Audit unsafe code
- Keep unsafe blocks as small as possible.
- Add comments describing safety invariants.
- Test edge cases and invalid inputs.
- Use established crates instead of reimplementing low-level primitives.
- Review unsafe changes carefully in code review.
17. Mini lab
Create a tiny Rust library with one safe function. Export a C-compatible addition function, build it as a library and inspect its symbol. Separately compile a pure calculation function to wasm32-unknown-unknown. Keep all unsafe code limited to the FFI boundary.
Key takeaways
unsafe is an explicit trust boundary, not a performance switch. Use it only when necessary, document invariants, isolate it behind safe APIs and treat FFI/Wasm boundaries as places where validation and ownership must be especially clear.
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
Learn how Kubernetes Deployments and ReplicaSets work with practical YAML, scaling, self-healing, rolling upda...
7 min read · Sep 17, 2026
Learn Kubernetes monitoring and logs with kubectl logs, events, metrics-server, top commands, Prometheus, Graf...
5 min read · Sep 17, 2026
Learn Kubernetes Ingress with controllers, IngressClass, host and path routing, TLS, DNS, annotations, redirec...
8 min read · Sep 17, 2026
Learn Kubernetes ConfigMaps and Secrets with YAML, environment variables, file mounts, update behavior, immuta...
8 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.