<< All versions

Skill v1.0.1

currentAutomated scan100/100
huiali/rust-skills/rust-unsafe

4 files

──Details
PublishedOctober 29, 2026 at 07:29 AM
Content Hashsha256:0e96899b762295a8...
Git SHAb712d518d518
Bump Typepatch
Compare with v1.0.0
──Files
Files (1 file, 10.4 KB)
SKILL.md10.4 KBactive
SKILL.md · 414 lines · 10.4 KB

name: rust-unsafe description: Unsafe code and FFI expert covering raw pointers (mut, const), FFI patterns, transmute, union, #[repr(C)], SAFETY comments, soundness rules, and undefined behavior prevention. metadata: version: "1.0.1" triggers:

  • unsafe
  • raw pointer
  • FFI
  • extern
  • transmute
  • union
  • repr(C)
  • MaybeUninit
  • NonNull
  • SAFETY comment
  • soundness
  • undefined behavior
  • UB

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:

rust
// 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

rust
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

rust
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

rust
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

rust
#[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

bash
# 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

bash
# 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

rust
// ❌ 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

rust
// ❌ 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

rust
// ❌ 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 - 完整中文版本,包含所有内容
← v1.0.0All versions