MCPcopy Index your code
hub / github.com/engali94/movable-ref

github.com/engali94/movable-ref @v0.2.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.2.0 ↗ · + Follow
106 symbols 165 edges 19 files 14 documented · 13%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

movable-ref

Crates.io Documentation CI MSRV

Movable self-referential data for Rust without pinning or runtime bookkeeping.

At a glance

  • Store offsets instead of absolute pointers so your data can move freely across stack, heap, arenas, or embedded buffers.
  • Works in no_std projects and can be tuned to an 8-bit offset for tightly packed layouts.
  • Core API is explicit—helper macros are available but completely optional.
  • Optional debug-guards feature adds runtime assertions while you are iterating; release builds stay lean.

When to reach for SelfRef

  • You need a self-referential struct that must move (e.g., push onto a Vec, relocate across buffers, or compact in place).
  • You are targeting embedded or real-time systems where every byte matters and heap allocation is either expensive or unavailable.
  • You want predictable behaviour and explicit control instead of macro-generated code or hidden reference counting.

Quick start

Install the crate:

[dependencies]
movable-ref = "0.1.0"

Wrap a field in SelfRefCell to keep the unsafe details contained:

use movable_ref::SelfRefCell;

struct Message {
    body: SelfRefCell<String, i16>,
}

impl Message {
    fn new(body: String) -> Self {
        Self { body: SelfRefCell::new(body).expect("offset fits in i16") }
    }

    fn body(&self) -> &str {
        self.body.get()
    }

    fn body_mut(&mut self) -> &mut String {
        self.body.get_mut()
    }
}

let mut msg = Message::new("move me".into());
assert_eq!(msg.body(), "move me");

let mut together = Vec::new();
together.push(msg);          // moved to heap inside Vec
assert_eq!(together[0].body(), "move me");

For advanced scenarios you can work with SelfRef directly, but doing so means reasoning about raw pointers. The recommended path is to use SelfRefCell inside your types and expose regular safe methods, as shown above.

How it works

Rust normally stores raw pointers. Absolute addresses break the moment a struct moves. SelfRef<T, I> stores only the signed offset (I) between the pointer and the value it targets plus the metadata needed to rebuild fat pointers ([T], str, trait objects).

When the owner moves, the relative distance stays the same, so recomputing the pointer after the move just works. Choose I to match the size of your container: i8 covers ±127 bytes, i16 covers ±32 KiB, isize covers most use cases.

Safety model

SelfRef uses unsafe internally, so it is important to follow the invariants:

  1. Initialise immediately: call SelfRef::set right after constructing the struct. The pointer stays unset otherwise.
  2. Keep layout stable: do not reorder or remove the referenced field after initialisation.
  3. Move the whole struct together: individual fields must not be detached from the container.

The crate provides layers to help you respect those rules:

  • SelfRefCell hides the unsafe parts and gives you safe try_get/try_get_mut accessors.
  • Enable the debug-guards feature during development to assert that recorded absolute pointers still match after moves.

Failure modes are documented in the crate root (src/lib.rs). Use the safe helpers whenever possible; unchecked calls are intended for tightly controlled internals.

Benchmarks

The Criterion benchmarks live in benches/performance.rs.

Operation Direct SelfRef Pin> Rc>
Access (ps) 329 331 365 429
Create (ns) 19 38 46 40
Move (ns) 49 58 N/A 50 (clone)

Memory usage per pointer:

SelfRef<T, i8>   : 1 byte (±127 bytes)
SelfRef<T, i16>  : 2 bytes (±32 KiB)
SelfRef<T, i32>  : 4 bytes (±2 GiB)
*const T         : 8 bytes
Rc<RefCell<T>>   : 8 bytes + heap allocation

cargo bench will rebuild these tables for your target.

Tooling

Task Command
Lint cargo clippy --all-targets -- -D warnings
Format cargo fmt
Tests cargo test
Miri cargo +nightly miri test (see full matrix below)
AddressSanitizer RUSTFLAGS="-Zsanitizer=address" ASAN_OPTIONS=detect_leaks=0 cargo +nightly test

Miri matrix:

cargo +nightly miri setup
cargo +nightly miri test
cargo +nightly miri test --no-default-features
cargo +nightly miri test --features nightly
cargo +nightly miri test --features debug-guards

Comparison

Approach Moves? Memory Runtime cost Notes
SelfRef 1–8 bytes None Works in no_std, flexible integer offsets
Pin<Box<T>> 8+ bytes Allocation Stable but data cannot move
Rc<RefCell<T>> ➖ (clone) 16+ bytes Borrow checking + refcount Allows interior mutability
ouroboros varies None Macro DSL, less manual control

License

MIT licensed. See LICENSE-MIT for details.

Extension points exported contracts — how you extend this code

PointerRecomposition (Interface)
The bridge that makes `SelfRef` work with any type, sized or unsized. Rust has two kinds of types: sized ones like `i32 [30 …
src/metadata/traits.rs
UncheckedOptionExt (Interface)
Adds an unchecked unwrap, this unwrap is UB if self is None # Safety This is UB if self is None and debug assertions a [1 …
src/pointer/unreachable.rs
Offset (Interface)
Trait for types that can represent pointer differences. Generalizes pointer arithmetic to integer types like `i8`, `i16
src/offset/delta.rs
Nullable (Interface)
A `Delta` type that has a null/zero value. # Safety Must satisfy: `add(NULL, ptr) == ptr` for all pointers.
src/offset/delta.rs

Core symbols most depended-on inside this repo

get_value
called by 7
benches/performance.rs
components_unchecked
called by 6
src/pointer/self_ref.rs
nn_to_ptr
called by 4
src/pointer/self_ref.rs
components_if_ready
called by 4
src/pointer/self_ref.rs
set
called by 4
src/pointer/self_ref.rs
is_ready
called by 3
src/pointer/self_ref.rs
as_non_null
called by 3
src/pointer/self_ref.rs
get_value
called by 3
examples/performance.rs

Shape

Method 53
Function 33
Class 14
Interface 4
Enum 2

Languages

Rust100%

Modules by API surface

src/pointer/self_ref.rs32 symbols
src/tests.rs27 symbols
benches/performance.rs10 symbols
examples/performance.rs8 symbols
src/metadata/trait_object.rs7 symbols
src/combinators/self_ref_cell.rs7 symbols
examples/basic_usage.rs4 symbols
src/error.rs3 symbols
src/pointer/unreachable.rs2 symbols
src/offset/delta.rs2 symbols
src/metadata/impls.rs2 symbols
src/pointer/operations.rs1 symbols

For agents

$ claude mcp add movable-ref \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page