In this lesson, you will learn how Rust’s high-level references interact with the rigid, low-level world of Application Binary Interfaces (ABIs).
Crossing the Border
When you write a Rust function, the compiler makes many guarantees about your references: they are non-null, properly aligned, and obey strict aliasing rules. However, when your Rust code communicates with another language (like C) through a Foreign Function Interface (FFI), those guarantees don't automatically follow.
An ABI is essentially a contract that dictates how data is arranged in memory so that two different programs can talk to each other. When you pass a Rust reference (&T) to a C function, Rust "downgrades" that reference to a raw pointer. Because the compiler cannot verify what the C code does with that memory, it has to treat the reference as an "unsafe" pointer.
The Dangers of Assumptions
Consider this snippet:
// Rust expects this pointer to point to a valid, aligned i32
pub extern "C" fn process_data(ptr: *const i32) {
unsafe {
// We are crossing the ABI boundary here!
let value = *ptr;
println!("Value: {}", value);
}
}
When you cross the ABI boundary, you lose the safety of the Borrow Checker. If the C code modifies the memory while Rust is reading it, or if it passes a null pointer, you trigger undefined behavior. Rust’s compiler-enforced lifetimes effectively "stop" at the boundary of your program; the ABI has no concept of Rust’s borrow rules or lifetimes.
Best Practices for FFI
To bridge this gap, you should treat all references received from an ABI boundary as potentially dangerous raw pointers. Before turning them back into safe Rust references using &*ptr, you must manually verify:
- Nullability: Check that the pointer is not null.
- Alignment: Ensure the address satisfies
std::mem::align_of::<T>(). - Initialization: Ensure the data has been properly initialized.
By treating the ABI boundary as a "danger zone," you ensure that the safety guarantees of your Rust code remain intact even when talking to external systems.
Key Takeaway: References crossing an ABI boundary are treated as raw, untrusted pointers, placing the burden of validating memory safety entirely on the developer.