Files
Sprimo/skills/unsafe-checker/rules/ffi-09-ref-not-ptr.md
2026-02-12 22:58:33 +08:00

137 lines
3.1 KiB
Markdown

---
id: ffi-09
original_id: P.UNS.FFI.09
level: P
impact: MEDIUM
---
# Use References Instead of Raw Pointers When Calling Safe C Functions
## Summary
When wrapping C functions that don't need null pointers, use Rust references in the safe wrapper to enforce non-null at compile time.
## Rationale
- References guarantee non-null
- References have lifetime tracking
- Raw pointers should stay in the unsafe FFI layer
- Safe Rust API should use safe types
## Bad Example
```rust
extern "C" {
fn c_process(data: *const u8, len: usize);
}
// DON'T: Expose raw pointers in safe API
pub fn process(data: *const u8, len: usize) {
// Caller might pass null!
unsafe { c_process(data, len); }
}
// DON'T: Unsafe function when it could be safe
pub unsafe fn process_unsafe(data: *const u8, len: usize) {
// Why force caller to use unsafe?
c_process(data, len);
}
```
## Good Example
```rust
extern "C" {
fn c_process(data: *const u8, len: usize);
fn c_modify(data: *mut Data);
fn c_optional(data: *const Data); // Can be null
}
// DO: Use slice reference for safe API
pub fn process(data: &[u8]) {
// Reference guarantees non-null
// Slice guarantees valid length
unsafe { c_process(data.as_ptr(), data.len()); }
}
// DO: Use &mut for exclusive access
pub fn modify(data: &mut Data) {
// Mutable reference guarantees:
// - Non-null
// - Exclusive access
// - Valid for duration
unsafe { c_modify(data as *mut Data); }
}
// DO: Use Option<&T> for nullable parameters
pub fn optional(data: Option<&Data>) {
let ptr = data.map(|d| d as *const Data).unwrap_or(std::ptr::null());
unsafe { c_optional(ptr); }
}
// DO: Wrap FFI types in safe Rust types
pub struct SafeHandle(*mut c_void);
impl SafeHandle {
pub fn new() -> Option<Self> {
let ptr = unsafe { create_handle() };
if ptr.is_null() {
None
} else {
Some(Self(ptr))
}
}
// Methods take &self or &mut self, not raw pointers
pub fn do_something(&self) {
unsafe { handle_operation(self.0); }
}
}
```
## Converting Between References and Pointers
```rust
// Reference to pointer
fn ref_to_ptr(r: &Data) -> *const Data {
r as *const Data
}
fn mut_ref_to_ptr(r: &mut Data) -> *mut Data {
r as *mut Data
}
// Slice to pointer
fn slice_to_ptr(s: &[u8]) -> (*const u8, usize) {
(s.as_ptr(), s.len())
}
// Pointer to reference (unsafe)
unsafe fn ptr_to_ref<'a>(p: *const Data) -> &'a Data {
&*p
}
unsafe fn ptr_to_mut<'a>(p: *mut Data) -> &'a mut Data {
&mut *p
}
```
## When to Use Raw Pointers
- FFI declarations (`extern "C"`)
- Implementing the unsafe boundary layer
- When null is a valid value
- When the pointee might not be valid Rust (e.g., uninitialized)
## Checklist
- [ ] Can this parameter be a reference instead of a pointer?
- [ ] Am I checking for null in the unsafe layer?
- [ ] Is the safe API free of raw pointers?
- [ ] Do I use Option<&T> for nullable references?
## Related Rules
- `safety-06`: Don't expose raw pointers in public APIs
- `ffi-02`: Read std::ffi documentation