agentleFS
huialihuiali/rust-skills/skills/rust-unsafe/SKILL.md

rust-unsafe

Skill47 starsChanged 9 months agoMITView on GitHub

Unsafe code and FFI expert covering raw pointers (*mut, *const), FFI patterns, transmute, union, #[repr(C)], SAFETY comments, soundness rules, and undefined behavior prevention.

We split this skill into the rules it gives an agent

2 rules. 0 are common, 2 are distinctive, and 0 are very specific to this project.

  • 0

    Common

    Most of the 3 edited versions we read keep these.

  • 2

    Distinctive

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    When reviewing unsafe code:

    line 317
  2. Distinctive:

    rust-performance: - When unsafe optimization is justified

    line 405

“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

When Unsafe is Justified

Use CaseExampleJustified?
FFI calls to Cextern "C" { fn libc_malloc(size: usize) -> *mut c_void; }✅ Yes
Low-level abstractionsInternal implementation of Vec, Arc✅ Yes
Performance optimization (measured)Hot path with proven bottleneck⚠️ Verify first
Escaping borrow checkerDon'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)

RuleDescription
G-01Don't use unsafe to escape compiler safety checks
G-02Don't blindly use unsafe for performance
G-03Don't create "Unsafe" aliases for types/methods

Memory Layout (6 rules)

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

Raw Pointers (6 rules)

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

Unions (2 rules)

RuleDescription
U-01Avoid unions except for C interop
U-02Don't use union variants with different lifetimes

FFI (18 rules)

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

Safe Abstractions (11 rules)

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

I/O Safety (1 rule)

RuleDescription
I-01Ensure 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

ErrorFix
Null pointer dereferenceCheck for null before dereferencing
Use after freeEnsure lifetime validity
Data raceAdd synchronization
Alignment violationUse #[repr(C)], check alignment
Invalid bit patternUse MaybeUninit
Missing SAFETY commentAdd comment

Deprecated Patterns

DeprecatedModern Alternative
mem::uninitialized()MaybeUninit<T>
mem::zeroed() (for reference types)MaybeUninit<T>
Raw pointer arithmeticNonNull<T>, ptr::add
CString::new().unwrap().as_ptr()Store CString first
static mutAtomicT or Mutex
Manual extern declarationsbindgen

FFI Tools

DirectionTool
C → Rustbindgen
Rust → Ccbindgen
PythonPyO3
Node.jsnapi-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 - 完整中文版本,包含所有内容

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

Reviews can't be read right now.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.