A Rust library for offset based pointers that enable movable self-referential data structures.
Standard Rust cannot create self-referential structures that can be moved in memory:
// This is impossible in safe Rust
struct SelfRef<'a> {
data: String,
ptr: &'a str, // Cannot reference self.data
}
Existing solutions have many limitations:
Pin<Box<T>>
: Requires heap allocation, prevents movement.Rc<RefCell<T>>
: Runtime overhead, notSend
/Sync
ouroboros
: Complex macros, limited flexibility.
Offset pointers store offsets instead of absolute addresses, enabling self-referential structures that remain valid when moved:
use movable_ref::SelfRef;
struct Node {
value: String,
self_ref: SelfRef<String, i16>, // 2-byte offset instead of 8-byte pointer
}
impl Node {
fn new(value: String) -> Self {
let mut node = Self {
value,
self_ref: SelfRef::null(),
};
node.self_ref.set(&mut node.value).unwrap();
node
}
fn get_value(&self) -> &str {
unsafe { self.self_ref.as_ref_unchecked() }
}
}
// This works! The structure can be moved anywhere
let node = Node::new("Hello".to_string());
let boxed = Box::new(node); // ✓ Moves to heap
let mut vec = Vec::new();
vec.push(*boxed); // ✓ Moves again
println!("{}", vec[0].get_value()); // ✓ Still works!
Offset pointers solve a fundamental limitation in Rust: creating efficient, movable self-referential data structures. While other solutions exist, tether provides:
- Embedded Systems Friendly: Can run in very memory constrained devices.
- Movement freedom: Structures work on stack, heap, or anywhere unlike
Pin
- True zero-cost abstraction: Zero to Minimal runtime overhead
- Memory efficiency: 1-8 bytes vs 8+ bytes for alternatives
- Simplicity: Straightforward API without complex macros
Perfect for performance-critical applications, embedded systems, and anywhere you need self-referential structures that can move.
Add to your Cargo.toml
:
[dependencies]
movable-ref = "0.1.0"
use movable_ref::SelfRef;
// 1. Create structure with null pointer
let mut data = MyStruct {
value: "Hello".to_string(),
ptr: SelfRef::null(),
};
// 2. Set the relative pointer
data.ptr.set(&mut data.value).unwrap();
// 3. Dereference the pointer
let reference: &str = unsafe { data.ptr.as_ref_unchecked() };
no_std
: Works in embedded environments (disable defaultstd
feature)nightly
: Trait object support with nightly Rust
[dependencies]
movable-ref = { version = "0.1.0", default-features = false }
Run cargo bench
to see these results on your machine:
Direct Access: 329ps (baseline)
SelfRef: 331ps ⭐ FASTEST
Pin<Box<T>>: 365ps (+10% slower)
Rc<RefCell<T>>: 429ps (+30% slower)
SelfRef<T, i8>: 1 byte (±127 byte range)
SelfRef<T, i16>: 2 bytes (±32KB range)
SelfRef<T, i32>: 4 bytes (±2GB range)
*const T: 8 bytes (full address space)
Rc<RefCell<T>>: 8 bytes + heap allocation
Direct: 19ns (baseline)
SelfRef: 38ns (+100% but still fastest)
Rc<RefCell<T>>: 40ns
Pin<Box<T>>: 46ns
Direct move: 49ns
Rc<RefCell<T>>: 50ns (clone, not true move)
SelfRef move: 58ns ⭐ __TRUE MOVE SEMANTICS__
Pin<Box<T>>: N/A (cannot move!)
Key Takeaways:
- ✅ Zero-cost abstraction: SelfRef access is as fast as direct access
- ✅ Memory efficient: 1-8 bytes vs 8+ bytes for alternatives
- ✅ True movability: Unlike Pin<Box>, SelfRef can actually move
- ✅ No runtime overhead: No borrow checking like Rc<RefCell>
Solution | Move Cost | Memory | Runtime Cost | Complexity |
---|---|---|---|---|
SelfRef |
Zero | 1-8 bytes | Zero | Low |
Pin<Box<T>> |
Impossible | 8+ bytes | Allocation | Medium |
Rc<RefCell<T>> |
Cheap | 16+ bytes | Reference counting | High |
ouroboros |
Zero | Varies | Zero | High |
⚠️ Usesunsafe
for pointer dereferencing- ✅ Safe when structure layout doesn't change after pointer setup
- ✅ Safe for moving entire structures
- ✅ Extensively tested with Miri
Run the examples to see tether in action:
# Basic usage
cargo run --example basic_usage
# Performance benchmarks
cargo run --example performance
Licensed under MIT license.