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
- Distinctive: line 317
When reviewing unsafe code:
- Distinctive: line 405
rust-performance: - When unsafe optimization is justified
“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.
Files
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 violationsStep 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 tsanStep 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 --openCommon 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() }; // Safe3. 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 - 完整中文版本,包含所有内容
Other versions3 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 3
- adxptived/Rust-Skills★ 1same name
- po4yka/rust-skills★ 1same name
- dawidpereira/rust-skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository40 other files it gives its agents.
- .codex/AGENTS.mdAGENTS.md
- SKILL.mdSkill
- rust-actorSkill
- rust-anti-patternSkill
- rust-async-patternSkill
- rust-asyncSkill
- rust-authSkill
- rust-cacheSkill
- rust-codingSkill
- rust-concurrencySkill
- rust-constSkill
- rust-coroutineSkill
- rust-databaseSkill
- rust-distributedSkill
- rust-dpdkSkill
- rust-ebpfSkill
- rust-ecosystemSkill
- rust-embeddedSkill
- rust-error-advancedSkill
- rust-errorSkill
- rust-ffiSkill
- rust-gpuSkill
- rust-learnerSkill
- rust-lifetime-complexSkill
- rust-linear-typeSkill
- rust-macroSkill
- rust-middlewareSkill
- rust-mutabilitySkill
- rust-observabilitySkill
- rust-ownershipSkill
- rust-performanceSkill
- rust-pinSkill
- rust-resourceSkill
- rust-skill-indexSkill
- rust-skillSkill
- rust-testingSkill
- rust-type-drivenSkill
- rust-webSkill
- rust-xacmlSkill
- rust-zero-costSkill
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.

