Unsafe and FFI
Everything before this lesson is safe Rust: if it compiles, those memory rules hold. Unsafe is a small list of extra operations, and only after you can state why they are still sound. Foreign functions are the second section, because they are the usual reason to write that block.
Unsafe
unsafe does not switch off the borrow checker. It allows five things you must uphold yourself.
| Operation | You must guarantee |
|---|---|
| Dereference a raw pointer | The pointer is valid, aligned, and the borrow rules still hold |
| Call an unsafe function | You meet that function’s safety contract |
| Read or write a mutable static | No data race on that address |
Implement an unsafe trait, such as Send, by hand | The type really has the property the trait claims |
| Read a union field | You read the field that was last written |
Production code reaches for unsafe in these places. Undefined behavior means the compiler may assume that path never happens. The symptom is not a tidy panic.
| Place | Why |
|---|---|
| A foreign-function boundary | The other language does not follow Rust’s rules |
| A structure you measured as too slow | Only after a measurement, not a guess |
| The operating system | The call is specified outside safe Rust |
A type you know is Send | The compiler cannot see the reason itself |
Callers of a safe function must not be able to cause undefined behavior. The check belongs outside the block.
fn first_unchecked(xs: &[i32]) -> Option<i32> {
if xs.is_empty() {
return None;
}
Some(unsafe {
// SAFETY: length was checked above, so index 0 is in range.
*xs.get_unchecked(0)
})
}
fn main() {
println!("{:?}", first_unchecked(&[4, 5]));
}Foreign functions
extern "C" declares a function another language can call, or one you call. The layout must be repr(C) so field order matches C. A Rust String is not a C string. A CString owns the bytes and the trailing zero. Who frees the pointer has to be written down: if C allocated it, C’s free releases it. bindgen generates the declarations. no_mangle keeps the symbol name. A panic that unwinds across the foreign boundary is undefined behavior. Catch it at the edge, or abort.
Null pointers, lengths, and UTF-8 are checked before the call. The unsafe block is the call itself.
fn main() {
println!("extern C and repr(C) are the boundary, not a second language");
println!("the safe side checks pointers; the unsafe side calls across");
println!("a panic must not unwind through foreign code");
}