rust-unsafe

📁 huiali/rust-skills 📅 Jan 30, 2026
4
总安装量
4
周安装量
#53725
全站排名
安装命令
npx skills add https://github.com/huiali/rust-skills --skill rust-unsafe

Agent 安装分布

claude-code 4
gemini-cli 3
qoder 2
antigravity 2
codebuddy 2
windsurf 2

Skill 文档

When Unsafe is Justified

Use Case Example Justified?
FFI calls to C extern "C" { fn libc_malloc(size: usize) -> *mut c_void; } ✅ Yes
Low-level abstractions Internal implementation of Vec, Arc ✅ Yes
Performance optimization (measured) Hot path with proven bottleneck ⚠️ Verify first
Escaping borrow checker Don’t know why you need it ❌ No

SAFETY Comment Requirements

Every unsafe block must include a SAFETY comment:

// SAFETY: ptr must be non-null and properly aligned.
// This function is only called after a null check.
unsafe { *ptr = value; }

/// # Safety
///
/// * `ptr` must be properly aligned and not null
/// * `ptr` must point to initialized memory of type T
/// * The memory must not be accessed after this function returns
pub unsafe fn write(ptr: *mut T, value: &T) { ... }

Solution Patterns

Pattern 1: FFI with Safe Wrapper

use std::ffi::{CStr, CString};
use std::os::raw::c_char;

extern "C" {
    fn c_function(s: *const c_char) -> i32;
}

// ✅ Safe wrapper
pub fn safe_c_function(s: &str) -> Result<i32, Box<dyn Error>> {
    let c_str = CString::new(s)?;
    // SAFETY: c_str is a valid null-terminated string created from Rust data.
    // The pointer is valid for the duration of this call.
    let result = unsafe { c_function(c_str.as_ptr()) };
    Ok(result)
}

Pattern 2: Raw Pointer with Validation

use std::ptr::NonNull;

struct Buffer {
    ptr: NonNull<u8>,
    len: usize,
}

impl Buffer {
    pub fn write(&mut self, index: usize, value: u8) -> Result<(), String> {
        if index >= self.len {
            return Err("index out of bounds".to_string());
        }

        // SAFETY: We've checked index is within bounds.
        // ptr is NonNull and points to valid memory.
        unsafe {
            self.ptr.as_ptr().add(index).write(value);
        }
        Ok(())
    }
}

Pattern 3: Uninitialized Memory

use std::mem::MaybeUninit;

// ✅ Safe uninitialized memory handling
fn create_buffer(size: usize) -> Vec<u8> {
    let mut buffer: Vec<MaybeUninit<u8>> = Vec::with_capacity(size);

    for i in 0..size {
        buffer.push(MaybeUninit::new(0));
    }

    // SAFETY: All elements have been initialized to 0.
    unsafe { std::mem::transmute(buffer) }
}

// ❌ Avoid: deprecated pattern
fn bad_buffer(size: usize) -> Vec<u8> {
    let mut v = Vec::with_capacity(size);
    unsafe { v.set_len(size); }  // UB if not initialized!
    v
}

Pattern 4: Repr(C) for FFI

#[repr(C)]
pub struct Point {
    pub x: f64,
    pub y: f64,
}

#[repr(C)]
pub enum Status {
    Success = 0,
    Error = 1,
}

// SAFETY: Layout matches C struct exactly
extern "C" {
    fn process_point(p: *const Point) -> Status;
}

47 Unsafe Rules Reference

General Principles (3 rules)

Rule Description
G-01 Don’t use unsafe to escape compiler safety checks
G-02 Don’t blindly use unsafe for performance
G-03 Don’t create “Unsafe” aliases for types/methods

Memory Layout (6 rules)

Rule Description
M-01 Choose appropriate memory layout for struct/tuple/enum
M-02 Don’t modify memory variables of other processes
M-03 Don’t let String/Vec auto-deallocate memory from other processes
M-04 Prefer reentrant versions of C-API or syscalls
M-05 Use third-party crates for bit fields
M-06 Use MaybeUninit<T> for uninitialized memory

Raw Pointers (6 rules)

Rule Description
P-01 Don’t share raw pointers across threads
P-02 Prefer NonNull<T> over *mut T
P-03 Use PhantomData<T> to mark variance and ownership
P-04 Don’t dereference pointers cast to misaligned types
P-05 Don’t manually convert immutable pointers to mutable
P-06 Use ptr::cast instead of as for pointer casts

Unions (2 rules)

Rule Description
U-01 Avoid unions except for C interop
U-02 Don’t use union variants with different lifetimes

FFI (18 rules)

Rule Description
F-01 Avoid passing strings directly to C
F-02 Carefully read std::ffi types documentation
F-03 Implement Drop for wrapped C pointers
F-04 Handle panics across FFI boundaries
F-05 Use portable type aliases from std or libc
F-06 Ensure C-ABI string compatibility
F-07 Don’t implement Drop for types passed to extern code
F-08 Handle errors properly in FFI
F-09 Use references instead of raw pointers in safe wrappers
F-10 Exported functions must be thread-safe
F-11 Be careful with references to repr(packed) fields
F-12 Document invariant assumptions for C parameters
F-13 Ensure consistent data layout for custom types
F-14 FFI types should have stable layout
F-15 Validate robustness of external values
F-16 Separate data and code for C closures
F-17 Use opaque types instead of c_void
F-18 Avoid passing trait objects to C

Safe Abstractions (11 rules)

Rule Description
S-01 Be aware of memory safety issues from panics
S-02 Unsafe code authors must verify safety invariants
S-03 Don’t expose uninitialized memory in public APIs
S-04 Avoid double-free from panics
S-05 Consider safety when manually implementing Auto Traits
S-06 Don’t expose raw pointers in public APIs
S-07 Provide safe alternatives for performance
S-08 Returning mutable reference from immutable parameter is wrong
S-09 Add SAFETY comment before each unsafe block
S-10 Add Safety section to public unsafe function docs
S-11 Use assert! instead of debug_assert! in unsafe functions

I/O Safety (1 rule)

Rule Description
I-01 Ensure I/O safety when using raw handles

Workflow

Step 1: Question the Need

Do I really need unsafe?
  → Can I use safe abstractions?
  → Is this for FFI? (justified)
  → Is this for measured performance? (profile first)
  → Am I fighting the borrow checker? (redesign instead)

Step 2: Write SAFETY Comments

For every unsafe block:
1. Document preconditions
2. Explain why they hold
3. Reference invariants maintained

For public unsafe functions:
1. Add /// # Safety section
2. List all requirements
3. Document consequences of violations

Step 3: Validate with Tools

# Detect undefined behavior
cargo +nightly miri test

# Memory leak detection
valgrind ./target/release/program

# Data race detection
RUST_BACKTRACE=1 cargo test --features tsan

Step 4: Build Safe Wrappers

Raw unsafe code
  ↓
Safe private functions (validate inputs)
  ↓
Safe public API (no unsafe visible)

Common Errors and Fixes

Error Fix
Null pointer dereference Check for null before dereferencing
Use after free Ensure lifetime validity
Data race Add synchronization
Alignment violation Use #[repr(C)], check alignment
Invalid bit pattern Use MaybeUninit
Missing SAFETY comment Add comment

Deprecated Patterns

Deprecated Modern Alternative
mem::uninitialized() MaybeUninit<T>
mem::zeroed() (for reference types) MaybeUninit<T>
Raw pointer arithmetic NonNull<T>, ptr::add
CString::new().unwrap().as_ptr() Store CString first
static mut AtomicT or Mutex
Manual extern declarations bindgen

FFI Tools

Direction Tool
C → Rust bindgen
Rust → C cbindgen
Python PyO3
Node.js napi-rs
C++ cxx

Review Checklist

When reviewing unsafe code:

  • Unsafe usage is justified (FFI, low-level abstraction, measured perf)
  • Every unsafe block has SAFETY comment
  • Public unsafe functions document Safety requirements
  • Raw pointers are validated before dereferencing
  • No raw pointer sharing across threads without sync
  • FFI boundaries properly handle panics
  • Memory layout explicitly specified for FFI types
  • Uninitialized memory uses MaybeUninit
  • Safe public API wraps unsafe internals
  • Tested with Miri for undefined behavior

Verification Commands

# Check for undefined behavior
cargo +nightly miri test

# Run with address sanitizer
RUSTFLAGS="-Z sanitizer=address" cargo +nightly test

# Check FFI bindings
cargo check --features ffi

# Verify memory safety
valgrind --leak-check=full ./target/release/program

# Documentation check
cargo doc --no-deps --open

Common Pitfalls

1. Dangling Pointers

Symptom: Use-after-free, segfault

// ❌ Bad: pointer outlives data
fn bad() -> *const i32 {
    let x = 42;
    &x as *const i32  // Dangling!
}

// ✅ Good: proper lifetime management
fn good(x: &i32) -> *const i32 {
    x as *const i32  // Lifetime tied to input
}

2. Uninitialized Memory

Symptom: Undefined behavior, random values

// ❌ Bad: reading uninitialized memory
let x: i32;
unsafe { println!("{}", x); }  // UB!

// ✅ Good: use MaybeUninit
let mut x = MaybeUninit::<i32>::uninit();
x.write(42);
let x = unsafe { x.assume_init() };  // Safe

3. Invalid Repr

Symptom: FFI crashes, data corruption

// ❌ Bad: default repr with FFI
struct Point { x: f64, y: f64 }
extern "C" { fn use_point(p: Point); }

// ✅ Good: explicit C layout
#[repr(C)]
struct Point { x: f64, y: f64 }
extern "C" { fn use_point(p: Point); }

Related Skills

  • rust-ownership – Lifetime and borrowing fundamentals
  • rust-ffi – Advanced FFI patterns
  • rust-performance – When unsafe optimization is justified
  • rust-coding – SAFETY comment conventions
  • rust-concurrency – Thread-safe unsafe patterns

Localized Reference

  • Chinese version: SKILL_ZH.md – 完整中文版本,包含所有内容