agentleFS
full-stack-skillsfull-stack-skills/rust-skills/skills/rust-unsafe-ffi/SKILL.md

rust-unsafe-ffi

Skill8 starsChanged 43 days agoApache-2.0View on GitHub

Design, implement, audit, and test unsafe Rust and foreign-function boundaries, including raw pointers, validity and aliasing invariants, MaybeUninit, layout, Pin, manual Send and Sync, allocators, C ABI declarations, callbacks, ownership transfer, unwinding, and Edition 2024 unsafe syntax. Use when safe Rust cannot express the required memory or ABI operation; require minimal unsafe blocks, explicit safety contracts, safe wrappers, Miri where applicable, and real platform integration tests.

We split this skill into the rules it gives an agent

18 rules. 0 are common, 8 are distinctive, and 10 are very specific to this project.

  • 0

    Common

    Most of the 3 edited versions we read keep these.

  • 8

    Distinctive

    This author's own choices. Worth a look.

  • 10

    Very specific

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

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Treat unsafe as a proof obligation.

    line 9
  2. Very specific:

    Use this skill for raw pointers, NonNull, unsafe functions and traits, unions, MaybeUninit, ManuallyDrop, layout, pinning, allocators, …

    line 13
  3. Very specific:

    Use # Safety documentation for unsafe public APIs and // SAFETY: comments at proof sites.

    line 41
  4. Very specific:

    Use ptr.add only within the same allocated object or one-past it.

    line 53
  5. Very specific:

    Use MaybeUninit<T> while values may be uninitialized, and track exactly which elements were initialized before drop or assume_init.

    line 55
  6. Very specific:

    Prefer from_ne_bytes, pointer casts with checked layout, or explicit field conversion over transmute.

    line 56
  7. Very specific:

    Do not use ManuallyDrop to hide ownership ambiguity or double-drop risk.

    line 57
  8. Very specific:

    Use #[repr(C)] for C-compatible struct layout and explicit reprs for shared enums.

    line 61
  9. Very specific:

    Use #[repr(transparent)] only when its documented field restrictions hold.

    line 62
  10. Distinctive:

    Treat padding as potentially uninitialized.

    line 63
  11. Distinctive:

    Prefer established projection helpers or structural pinning patterns over raw self-referential pointers.

    line 65
  12. Very specific:

    Use std::ffi C types or generated bindings, not assumed Rust integer widths.

    line 89
  13. Distinctive:

    Read Unsafe and FFI Reference for memory-type details.

    line 109
  14. Distinctive:

    Do tests exercise null, empty, maximum, misaligned, error, callback, and shutdown paths?

    line 120
  15. Distinctive:

    Document every caller and implementation safety obligation.

    line 124
  16. Distinctive:

    Keep unsafe blocks minimal and safe wrappers impossible to misuse from safe Rust.

    line 125
  17. Distinctive:

    Verify ABI types, layout, ownership, error, and unwinding behavior.

    line 126
  18. Distinctive:

    Run Miri where applicable and real platform tests for the external boundary.

    line 127

“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

Rust Unsafe and FFI

Treat unsafe as a proof obligation. An unsafe block permits specific operations; it does not relax validity, aliasing, initialization, lifetime, data-race, layout, or ABI requirements.

Scope and Routing

Use this skill for raw pointers, NonNull, unsafe functions and traits, unions, MaybeUninit, ManuallyDrop, layout, pinning, allocators, manual Send or Sync, C ABI bindings, callbacks, handles, and ownership transfer.

Route ordinary ownership design to rust-stable, concurrent architecture to rust-concurrency, UniFFI-generated multi-language bindings to rust-uniffi-building, general build scripts to rust-cargo-build, and review reporting to rust-code-review.

Workflow

1. Justify and isolate unsafe operations

Identify the operation safe Rust cannot express. Prefer an audited crate or standard-library abstraction when it preserves the required behavior. Keep unsafe blocks small and enable:

#![deny(unsafe_op_in_unsafe_fn)]

An unsafe fn must still place each unsafe operation in an explicit unsafe {} block. Expose a safe wrapper only when it can establish and preserve every invariant internally.

2. Write the safety contract before code

Document:

  • pointer provenance, non-nullness, alignment, and dereferenceable byte range;
  • initialization and validity requirements for the pointee type;
  • aliasing and mutation rules for the full access duration;
  • lifetime and ownership transfer, including who destroys or frees values;
  • thread-safety and reentrancy requirements;
  • ABI, layout, calling convention, integer width, and error conventions;
  • panic or foreign-exception behavior across the boundary.

Use # Safety documentation for unsafe public APIs and // SAFETY: comments at proof sites.

3. Implement raw-memory operations conservatively

pub unsafe fn read_i32(ptr: *const i32) -> i32 {
    // SAFETY: The caller guarantees that ptr is aligned, initialized,
    // dereferenceable for one i32, and not concurrently mutated.
    unsafe { ptr.read() }
}
  • Use ptr.add only within the same allocated object or one-past it.
  • Distinguish aligned and unaligned reads; never create a reference to an unaligned packed field.
  • Use MaybeUninit<T> while values may be uninitialized, and track exactly which elements were initialized before drop or assume_init.
  • Prefer from_ne_bytes, pointer casts with checked layout, or explicit field conversion over transmute.
  • Do not use ManuallyDrop to hide ownership ambiguity or double-drop risk.

4. Define layout and pinning precisely

  • Use #[repr(C)] for C-compatible struct layout and explicit reprs for shared enums.
  • Use #[repr(transparent)] only when its documented field restrictions hold.
  • Treat padding as potentially uninitialized; do not serialize a struct by copying its raw bytes.
  • Pin<P> protects the pointee only under the pinning contract. It does not make ordinary Unpin data immovable and does not by itself make a self-reference sound.
  • Prefer established projection helpers or structural pinning patterns over raw self-referential pointers.

5. Build a narrow FFI boundary

Edition 2024 requires unsafe extern blocks and unsafe attributes where applicable:

use std::ffi::{c_char, CStr};

unsafe extern "C" {
    fn foreign_name() -> *const c_char;
}

pub fn name() -> Option<String> {
    // SAFETY: The foreign contract promises either null or a valid,
    // NUL-terminated string that remains alive for this call.
    let ptr = unsafe { foreign_name() };
    (!ptr.is_null()).then(|| {
        // SAFETY: Non-nullness and termination follow from the contract above.
        unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned()
    })
}
  • Use std::ffi C types or generated bindings, not assumed Rust integer widths.
  • Convert nullable pointers, lengths, ownership, and error codes at one boundary.
  • Prevent unwinding across a C ABI unless the selected ABI explicitly permits it; catch panics at callbacks when necessary.
  • Make callback lifetime, thread, cancellation, and unregister behavior explicit.
  • Pair allocation and deallocation in the same allocator domain.

6. Verify the proof

Run applicable gates:

cargo fmt --all --check
cargo check --workspace --all-targets --all-features
cargo test --workspace --all-targets --all-features
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo miri test

Also use sanitizers or Valgrind where supported, ABI layout assertions, C-side integration tests, multiple optimization levels, and every supported operating system and architecture. Miri does not execute arbitrary foreign code, so isolate or mock the external call while testing the Rust-side memory contract.

Read Unsafe and FFI Reference for memory-type details. Read Execution Scenarios for representative boundaries.

Review Checklist

  • Is unsafe necessary, minimal, and locally justified?
  • Does each safety contract cover validity, aliasing, lifetime, layout, and concurrency?
  • Can a safe caller violate the wrapper's assumptions?
  • Are partial initialization and failure cleanup correct?
  • Are ownership and deallocation symmetric across FFI?
  • Can panic or foreign unwinding cross the ABI boundary?
  • Are manual Send and Sync implementations proven for every field and callback?
  • Do tests exercise null, empty, maximum, misaligned, error, callback, and shutdown paths?

Completion Criteria

  • Document every caller and implementation safety obligation.
  • Keep unsafe blocks minimal and safe wrappers impossible to misuse from safe Rust.
  • Verify ABI types, layout, ownership, error, and unwinding behavior.
  • Run Miri where applicable and real platform tests for the external boundary.
  • Record any property that could not be verified locally.

Upstream Sources

Data Privacy

This skill does not collect, store, or transmit user data. Do not send proprietary headers, generated bindings, crash dumps, or memory contents to external services without authorization.

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.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

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.