rust-unsafe
Unsafe & FFI
Safety Rules
| Rule | Guideline |
|---|---|
unsafe only for UB risk (not "dangerous") |
M-UNSAFE-IMPLIES-UB |
| Must have valid reason: novel abstraction, perf, FFI | M-UNSAFE |
| All code must be sound — no exceptions | M-UNSOUND |
| Must pass Miri, include safety comments | M-UNSAFE |
Never use unsafe to escape borrow checker |
general-01 |
Never use unsafe blindly for performance |
general-02 |
| No aliasing violations | general-03 |
Before Writing Unsafe — Checklist
- Do you really need unsafe? Try safe alternatives first: restructure for borrow checker, use
Cell/RefCell/Mutex, check for a safe crate - Identify the operation: pointer deref,
unsafe fncall, mutable static, unsafe trait impl, union field, FFI - Document invariants with
// SAFETY:comments explaining what must hold and why it does - Test with Miri:
cargo miri test
Top 10 Unsafe Pitfalls
| # | Pitfall | Detection | Fix |
|---|---|---|---|
| 1 | Dangling pointer from local | Miri | Heap-allocate or return value |
| 2 | CString dropped, pointer dangling | Miri | Caller keeps CString alive, or into_raw |
| 3 | Vec::set_len with uninitialized data |
Miri | Use push/resize instead |
| 4 | Reference to #[repr(packed)] field |
Miri, UBsan | read_unaligned via addr_of! |
| 5 | Mutable aliasing through raw pointers | Miri | Use single pointer, sequential access |
| 6 | transmute to wrong size |
Compile error/Miri | Use as conversion |
| 7 | Invalid enum discriminant via transmute | Manual review | Use match or TryFrom |
| 8 | Panic unwinding across FFI boundary | Testing | Wrap with catch_unwind |
| 9 | Double free from Clone + Drop on raw ptr |
ASan | Don't impl Clone, or use refcounting |
| 10 | mem::forget prevents destructor (lock leak) |
Manual review | Let guard drop naturally |
Unsafe Rules by Category
| Category | Count | Key Rules |
|---|---|---|
| General Principles | 3 | No borrow-checker escape, no perf-only unsafe, no aliasing |
| Safety Abstraction | 11 | Panic safety, invariant verification, Send/Sync soundness, SAFETY comments |
| Raw Pointers | 6 | Prefer NonNull, use PhantomData for variance, check alignment, never cast *const to *mut |
| Memory Layout | 6 | #[repr(C)] for FFI, use MaybeUninit (not mem::uninitialized), check reentrancy |
| FFI | 18 | No direct String, proper CString/CStr, Drop for C ptrs, panic boundaries, portable types |
| Union | 2 | Avoid except for FFI, no cross-lifetime unions |
| I/O Safety | 1 | Raw handle ownership |
// SAFETY comment examples:
// SAFETY: We checked that index < len above, so this is in bounds.
// SAFETY: The pointer was created from a valid reference and hasn't been invalidated.
// SAFETY: We hold the lock, guaranteeing exclusive access.
// SAFETY: The type is #[repr(C)] and all fields are initialized.
Safe Abstraction Pattern
Wrap unsafe in safe public APIs:
pub struct CBuffer {
ptr: NonNull<u8>,
len: usize,
}
impl CBuffer {
pub fn new(size: usize) -> Option<Self> {
let ptr = unsafe { c_alloc(size) };
NonNull::new(ptr).map(|ptr| Self { ptr, len: size })
}
pub fn as_slice(&self) -> &[u8] {
// SAFETY: ptr is valid for len bytes (from c_alloc contract)
unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
}
}
impl Drop for CBuffer {
fn drop(&mut self) {
unsafe { c_free(self.ptr.as_ptr()); }
}
}
Key principles: encapsulate unsafe behind safe API, use PhantomData for lifetime tracking, use Drop for cleanup, use private fields to maintain invariants.
FFI Patterns
Key FFI Rules
- Always use
#[repr(C)]for types crossing FFI - Handle null pointers at the boundary
- Catch panics before returning to C (
catch_unwind) - Document ownership clearly (who allocates, who frees)
- Use opaque types for type safety
- No
Stringin FFI — useCString/CStr - Use portable types (
c_int,c_char, etc.)
FFI Wrapper Pattern
// Raw C API in private module
mod ffi {
extern "C" {
pub fn lib_create(name: *const c_char) -> *mut c_void;
pub fn lib_destroy(handle: *mut c_void);
}
}
// Safe public wrapper with RAII
pub struct Library {
handle: NonNull<c_void>,
}
impl Library {
pub fn new(name: &str) -> Result<Self, LibraryError> {
let c_name = CString::new(name).map_err(|_| LibraryError("invalid name".into()))?;
let handle = unsafe { ffi::lib_create(c_name.as_ptr()) };
NonNull::new(handle)
.map(|handle| Self { handle })
.ok_or_else(|| LibraryError("creation failed".into()))
}
}
impl Drop for Library {
fn drop(&mut self) {
unsafe { ffi::lib_destroy(self.handle.as_ptr()); }
}
}
Error Handling Across FFI
// BAD: panic unwinding across FFI is UB
#[no_mangle]
extern "C" fn callback(x: i32) -> i32 {
if x < 0 { panic!("negative!"); } // UB!
x * 2
}
// GOOD: catch panics at FFI boundary
#[no_mangle]
extern "C" fn callback(x: i32) -> i32 {
std::panic::catch_unwind(|| {
if x < 0 { panic!("negative!"); }
x * 2
}).unwrap_or(-1) // error code on panic
}
Opaque Handle Types
// Prevent mixing up handles with phantom types
#[repr(C)]
pub struct DatabaseHandle {
_data: [u8; 0],
_marker: PhantomData<(*mut u8, PhantomPinned)>,
}
pub struct Database { handle: NonNull<DatabaseHandle> }
pub struct Connection<'db> {
handle: NonNull<ConnectionHandle>,
_db: PhantomData<&'db Database>, // lifetime ties to Database
}
Callback Registration
pub struct CallbackGuard<F> {
_closure: Box<F>,
}
impl<F: FnMut(i32) -> i32 + 'static> CallbackGuard<F> {
pub fn register(closure: F) -> Self {
let boxed = Box::new(closure);
let user_data = Box::into_raw(boxed) as *mut c_void;
extern "C" fn trampoline<F: FnMut(i32) -> i32>(
value: c_int, user_data: *mut c_void,
) -> c_int {
catch_unwind(AssertUnwindSafe(|| {
let closure = unsafe { &mut *(user_data as *mut F) };
closure(value as i32) as c_int
})).unwrap_or(-1)
}
unsafe { register_callback(trampoline::<F>, user_data); }
Self { _closure: unsafe { Box::from_raw(user_data as *mut F) } }
}
}
impl<F> Drop for CallbackGuard<F> {
fn drop(&mut self) {
unsafe { unregister_callback(); }
}
}
C-Compatible Structs
#[repr(C)]
pub struct Config {
pub version: c_int,
pub flags: u32,
pub name: [c_char; 64],
pub name_len: usize,
}
// Verify layout at compile time
const _: () = {
assert!(std::mem::size_of::<Config>() == 80);
assert!(std::mem::align_of::<Config>() == 8);
};
More from peixotorms/odinlayer-skills
elementor-development
Use when building Elementor addons, creating custom widgets, or managing Elementor components. Covers Widget_Base class (get_name, get_title, get_icon, register_controls, render, content_template), widget registration via elementor/widgets/register hook, addon structure and plugin header, wp_enqueue_script for widget assets, get_script_depends, get_style_depends, inline editing toolbars, custom widget categories, manager registration (register/unregister), selector tokens ({{WRAPPER}}), deprecation handling, and Elementor CLI commands.
65elementor-hooks
Use when hooking into Elementor lifecycle events, injecting controls, filtering widget output, or using the JS APIs. Covers elementor/init, elementor/element/before_section_end, elementor/element/after_section_end, elementor/widget/render_content filter, elementor/frontend/after_enqueue_styles, frontend JS hooks (elementorFrontend.hooks, frontend/element_ready), editor JS hooks (elementor.hooks), $e.commands API ($e.run, $e.commands.register), $e.routes, $e.hooks (registerUIBefore, registerUIAfter), control injection patterns, CSS file hooks, forms hooks (Pro), and query filters.
26elementor-themes
Use when building Elementor-compatible themes, registering theme locations, creating dynamic tags, or extending the Finder. Covers register_location, theme_builder locations, elementor_theme_do_location, Theme_Document and theme conditions, Tag_Base for dynamic tags (register_tag, get_value, render), Finder extension (Category_Base, register via elementor/finder/register), Context_Menu customization (elements/context-menu/groups filter), Hello Elementor theme (elementor-hello-theme, hello_elementor_* filters), and hosting page cache integration hooks.
25elementor-controls
Use when adding controls to Elementor widgets, creating custom controls, or referencing control type parameters. Covers add_control with types (TEXT, SELECT, SLIDER, COLOR, MEDIA, REPEATER, CHOOSE, NUMBER, SWITCHER, URL, ICONS), TYPOGRAPHY and BACKGROUND group controls, BORDER, BOX_SHADOW group controls, add_responsive_control, add_group_control, CSS selectors ({{WRAPPER}}, {{VALUE}}), condition and conditions for conditional display, dynamic content tags, POPOVER_TOGGLE, and global styles integration.
16elementor-forms
Use when creating custom Elementor form actions, custom form field types, form validation, or processing form submissions. Covers Elementor Pro forms (ElementorPro\Modules\Forms), Action_Base (get_name, get_label, run, register_settings_section, on_export), after_submit processing, Field_Base (field_type, render field HTML, validation callback, update_controls), content_template for editor preview, form action registration, export_type handling, update_record patterns, elementor_pro/forms/validation hook, email filters, and webhook response handling.
12flyonui
Use when building with FlyonUI — Tailwind CSS component library with CSS classes and optional JS plugins. Covers CSS component classes, JS plugin system (accordion, carousel, collapse, combobox, datatable, dropdown, select, tabs, tooltip, etc.), theming, installation, class reference, and plugin initialization via MCP tools.
9