---
title: Raw Pointers and FFI
url: https://doc.liz6.com/en/rust/07-unsafe-rust/01-raw-pointers-and-ffi
locale: en
area: rust
tags:
- rust
- unsafe-rust
date: 2026-06-30
modified: 2026-07-16
description: 'const T and mut T are not constrained by the borrow checker—they can be aliased freely and may not point to valid memory. Their sole purpose is FFI: calling C libraries, receiving C callbacks, and manipulating hardware registers. An unsafe block does not "disable safety checks"; rather, it shifts safety responsibility from the compiler to the programmer.'
---

# Raw Pointers and FFI

> `*const T` and `*mut T` are not constrained by the borrow checker—they can be aliased freely and may not point to valid memory. Their sole purpose is FFI: calling C libraries, receiving C callbacks, and manipulating hardware registers. An unsafe block does not "disable safety checks"; rather, it shifts safety responsibility from the compiler to the programmer.

## Raw Pointers: Pointers Without a Borrow Checker

```rust
let x = 42;
let ptr: *const i32 = &x;            // Reference → Raw Pointer (safe)
unsafe {
    println!("{}", *ptr);            // Dereference (unsafe — compiler does not validate validity)
}
```

Raw pointers do not need to adhere to borrow checker rules—you can have multiple `*mut T` pointing to the same data, they can be null, and alignment is not guaranteed. All responsibility shifts to the programmer.

Difference from C `int *`: Rust distinguishes between `*const T` and `*mut T` (similar to C's `const int *` vs `int *`). However, C can implicitly discard const, whereas Rust cannot.

## extern "C": C Function Interface

```rust
extern "C" {
    fn abs(input: i32) -> i32;       // Declare C function
}

#[no_mangle]                         // Prevent Rust name mangling
pub extern "C" fn rust_callback(x: i32) -> i32 {
    x + 1                            // C code can call this function
}
```

`extern "C"` uses the C calling convention: arguments are arranged in registers/stack according to the target platform's C ABI. On x86-64, this is the System V AMD64 ABI (arguments passed via rdi, rsi, rdx, rcx, r8, r9). You can also use `extern "system"` (Windows API) and `extern "Rust"` (default).

## #[repr(C)]: C-Compatible Memory Layout

```rust
#[repr(C)]
struct MyStruct {
    a: i32,                          // offset 0
    // padding: 4 bytes               // C requires f64 to be aligned to 8
    b: f64,                          // offset 8
}                                    // size: 16 bytes
```

Without `#[repr(C)]`, the Rust compiler may reorder fields to optimize struct size. With `#[repr(C)]`, fields are ordered as declared, with padding added according to C ABI requirements—ensuring binary compatibility with C structs. `#[repr(C, packed)]` eliminates all padding (but may create unaligned references—unsafe).

## bindgen: Automatically Generate Rust FFI Bindings

Generate Rust `extern` declarations, struct definitions, and constants from C headers:

```rust
// build.rs:
fn main() {
    println!("cargo:rerun-if-changed=wrapper.h");
    let bindings = bindgen::Builder::default()
        .header("wrapper.h")
        .allowlist_function("my_lib_.*")   // Only bind these functions
        .generate().expect("bindgen failed");
    bindings.write_to_file("src/bindings.rs").unwrap();
}
// src/lib.rs:
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
```

## Callbacks: The Trap of C Calling Rust

```rust
extern "C" fn callback(x: i32) {
    println!("C called with {}", x);
}
unsafe { register_callback(callback as unsafe extern "C" fn(i32)); }
```

When passing Rust functions as callbacks to C, **panics cannot cross FFI boundaries**—if a panic occurs within the callback and unwinding crosses the C stack frame, this is UB (C stacks do not have unwind tables). Solutions: catch panics within the callback using `std::panic::catch_unwind`, or set `panic = "abort"` in Cargo.toml.

## References

- **Rustonomicon**: FFI, C ABI
- **bindgen**: rust-lang.github.io/rust-bindgen
- **The Embedded Rust Book**: FFI and C interaction

*Keywords: raw pointer, *const, *mut, extern "C", #[no_mangle], repr(C), bindgen, FFI callback, C ABI*
