Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,29 @@ jobs:

- name: Check MSRV
run: cargo check

miri:
name: Miri
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust nightly with Miri
uses: dtolnay/rust-toolchain@nightly
with:
components: miri

- name: Cache dependencies
uses: Swatinem/rust-cache@v2

- name: Miri setup
run: cargo +nightly miri setup

- name: Miri tests default
run: cargo +nightly miri test

- name: Miri tests no_std
run: cargo +nightly miri test --no-default-features

- name: Miri tests nightly features
run: cargo +nightly miri test --features nightly
34 changes: 24 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,19 +87,22 @@ movable-ref = "0.1.0"
## Basic Usage

```rust
use movable_ref::SelfRef;
use movable_ref::{SelfRefCell, selfref_accessors};

struct MyStruct {
value: SelfRefCell<String, i16>,
}

// 1. Create structure with null pointer
let mut data = MyStruct {
value: "Hello".to_string(),
ptr: SelfRef::null(),
};
impl MyStruct {
fn new(s: String) -> Self {
Self { value: SelfRefCell::new(s).unwrap() }
}
}

// 2. Set the relative pointer
data.ptr.set(&mut data.value).unwrap();
selfref_accessors!(impl MyStruct { value_ref, value_mut: value -> String });

// 3. Dereference the pointer
let reference: &str = unsafe { data.ptr.as_ref_unchecked() };
let mut data = MyStruct::new("Hello".to_string());
let reference: &str = data.value_ref();
```

## Features
Expand Down Expand Up @@ -171,6 +174,17 @@ Pin<Box<T>>: N/A (cannot move!)
- ✅ Safe for moving entire structures
- ✅ Extensively tested with Miri

## Miri checks

```bash
rustup toolchain install nightly
rustup component add miri --toolchain nightly
cargo +nightly miri setup
cargo +nightly miri test
cargo +nightly miri test --no-default-features
cargo +nightly miri test --features nightly
```

## Examples

Run the examples to see tether in action:
Expand Down
25 changes: 13 additions & 12 deletions benches/performance.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use criterion::{criterion_group, criterion_main, Criterion};
use movable_ref::SelfRef;
use movable_ref::{SelfRef, SelfRefCell};
use std::cell::RefCell;
use std::hint::black_box;
use std::pin::Pin;
Expand All @@ -10,22 +10,23 @@ use std::rc::Rc;
// ============================================================================

struct SelfRefStruct {
data: [u64; 100],
ptr: SelfRef<u64, i16>,
cell: SelfRefCell<[u64; 100], i16>,
}

impl SelfRefStruct {
fn new() -> Self {
let mut this = Self {
data: [42; 100],
ptr: SelfRef::null(),
};
this.ptr.set(&mut this.data[50]).unwrap();
this
let mut data = [0u64; 100];
data.iter_mut().enumerate().for_each(|(i, item)| {
*item = i as u64 * 2;
});

Self {
cell: SelfRefCell::new(data).unwrap(),
}
}

fn get_value(&self) -> u64 {
unsafe { *self.ptr.as_ref_unchecked() }
fn get_value(&self) -> &u64 {
&self.cell.get()[50]
}
}

Expand Down Expand Up @@ -158,7 +159,7 @@ fn bench_move_semantics(c: &mut Criterion) {
b.iter(|| {
let s = SelfRefStruct::new();
let moved = Box::new(s);
black_box(moved.get_value())
black_box(*moved.get_value())
})
});

Expand Down
24 changes: 9 additions & 15 deletions examples/basic_usage.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
#![allow(clippy::uninlined_format_args)]

use movable_ref::SelfRef;
use movable_ref::{selfref_accessors, SelfRefCell};

struct Node {
value: String,
self_ref: SelfRef<String, i16>,
value: SelfRefCell<String, i16>,
}

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
let value = SelfRefCell::new(value).unwrap();
Self { value }
}
}

fn get_value(&self) -> &str {
unsafe { self.self_ref.as_ref_unchecked() }
}
selfref_accessors!(impl Node { get_value, get_value_mut: value -> String });

impl Node {
fn len(&self) -> usize {
self.get_value().len()
}
Expand All @@ -36,9 +30,9 @@ fn main() {

let nodes = [*boxed_node, Node::new("Another node".to_string())];

for (i, node) in nodes.iter().enumerate() {
nodes.iter().enumerate().for_each(|(i, node)| {
println!("Node {}: '{}' (len: {})", i, node.get_value(), node.len());
}
});

println!("\nNote: These structures remain valid after all moves!");
}
30 changes: 14 additions & 16 deletions examples/performance.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,32 @@
#![allow(clippy::uninlined_format_args)]

use movable_ref::SelfRef;
use movable_ref::{selfref_accessors, SelfRef, SelfRefCell};
use std::hint::black_box;
use std::time::Instant;

struct SelfRefRelPtr {
data: [u64; 100],
ptr: SelfRef<u64, i16>,
ptr: SelfRefCell<u64, i16>,
}

impl SelfRefRelPtr {
fn new() -> Self {
let mut this = Self {
data: [0u64; 100],
ptr: SelfRef::null(),
ptr: SelfRefCell::new(0u64).unwrap(),
};

for (i, item) in this.data.iter_mut().enumerate() {
this.data.iter_mut().enumerate().for_each(|(i, item)| {
*item = i as u64 * 2;
}
});

this.ptr.set(&mut this.data[50]).unwrap();
this.ptr = SelfRefCell::new(this.data[50]).unwrap();
this
}

fn get_value(&self) -> u64 {
unsafe { *self.ptr.as_ref_unchecked() }
}
}

selfref_accessors!(impl SelfRefRelPtr { get_value : ptr -> u64 });

struct DirectAccess {
data: [u64; 100],
index: usize,
Expand All @@ -37,9 +35,9 @@ struct DirectAccess {
impl DirectAccess {
fn new() -> Self {
let mut data = [0u64; 100];
for (i, item) in data.iter_mut().enumerate() {
data.iter_mut().enumerate().for_each(|(i, item)| {
*item = i as u64 * 2;
}
});

Self { data, index: 50 }
}
Expand Down Expand Up @@ -96,15 +94,15 @@ fn benchmark_access_performance() {
let direct_struct = DirectAccess::new();

let start = Instant::now();
for _ in 0..ITERATIONS {
(0..ITERATIONS).for_each(|_| {
black_box(rel_ptr_struct.get_value());
}
});
let rel_ptr_time = start.elapsed();

let start = Instant::now();
for _ in 0..ITERATIONS {
(0..ITERATIONS).for_each(|_| {
black_box(direct_struct.get_value());
}
});
let direct_time = start.elapsed();

println!("Access Performance ({} iterations):", ITERATIONS);
Expand Down
37 changes: 37 additions & 0 deletions src/combinators/self_ref_cell.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use crate::offset::Nullable;
use crate::{Offset, PointerRecomposition, SelfRef};

/// Container that provides safe access to a self-referenced value.
pub struct SelfRefCell<T: PointerRecomposition, I: Offset = isize> {
value: T,
ptr: SelfRef<T, I>,
}

impl<T: PointerRecomposition, I: Offset + Nullable> SelfRefCell<T, I> {
/// Creates a new cell.
pub fn new(value: T) -> Result<Self, I::Error> {
Comment thread
engali94 marked this conversation as resolved.
let mut this = Self {
value,
ptr: SelfRef::null(),
};
this.ptr.set(&mut this.value)?;
Ok(this)
}

/// Immutable access to the value.
pub fn get(&self) -> &T {
let base = self as *const _ as *const u8;
unsafe { self.ptr.get_ref_from_base_unchecked(base) }
Comment thread
engali94 marked this conversation as resolved.
}
Comment thread
engali94 marked this conversation as resolved.

/// Mutable access to the value.
pub fn get_mut(&mut self) -> &mut T {
let base = self as *mut _ as *mut u8;
unsafe { self.ptr.get_mut_from_base_unchecked(base) }
}
Comment thread
engali94 marked this conversation as resolved.

/// Consumes the cell and returns the value.
pub fn into_inner(self) -> T {
self.value
}
}
16 changes: 11 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(feature = "nightly", feature(ptr_metadata))]
#![cfg_attr(feature = "nightly", feature(ptr_metadata, strict_provenance))]
#![allow(clippy::needless_doctest_main)]
#![forbid(missing_docs)]
#![deny(unused_must_use)]
Expand Down Expand Up @@ -86,21 +86,22 @@ impl SelfRefStruct {
this
}

pub fn fst(&self) -> &str {
unsafe { self.ptr.as_ref_unchecked() }
pub fn fst(&mut self) -> &str {
let base = self as *const _ as *const u8;
unsafe { self.ptr.get_ref_from_base_unchecked(base) }
}

pub fn snd(&self) -> u32 {
self.value.1
}
}

let s = SelfRefStruct::new("Hello World".into(), 10);
let mut s = SelfRefStruct::new("Hello World".into(), 10);

assert_eq!(s.fst(), "Hello World");
assert_eq!(s.snd(), 10);

let s = Box::new(s); // Force a move - relative pointers work on the heap
let mut s = Box::new(s); // Force a move - relative pointers work on the heap

assert_eq!(s.fst(), "Hello World");
assert_eq!(s.snd(), 10);
Expand Down Expand Up @@ -133,10 +134,15 @@ extern crate core as std;
mod tests;

mod error;
mod macros;
mod metadata;
mod offset;
mod pointer;
mod combinators {
pub mod self_ref_cell;
}

pub use self::combinators::self_ref_cell::SelfRefCell;
pub use self::error::*;
pub use self::metadata::*;
pub use self::offset::*;
Expand Down
21 changes: 21 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#[doc(hidden)]
#[macro_export]
macro_rules! selfref_accessors {
(impl $owner:ty { $get:ident, $get_mut:ident : $field:ident -> $t:ty }) => {
impl $owner {
pub fn $get(&self) -> &$t {
self.$field.get()
}
pub fn $get_mut(&mut self) -> &mut $t {
self.$field.get_mut()
}
}
};
(impl $owner:ty { $get:ident : $field:ident -> $t:ty }) => {
impl $owner {
pub fn $get(&self) -> &$t {
self.$field.get()
}
}
};
}
12 changes: 5 additions & 7 deletions src/metadata/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,10 +270,8 @@ unsafe impl<T> PointerRecomposition for [T] {

#[inline]
unsafe fn recompose(ptr: Ptr<u8>, data: Self::Components) -> Ptr<Self> {
Some(NonNull::from(std::slice::from_raw_parts_mut(
ptr?.as_ptr() as *mut T,
data,
)))
let ptr = ptr?.cast::<T>();
Some(NonNull::slice_from_raw_parts(ptr, data))
}
}

Expand All @@ -287,8 +285,8 @@ unsafe impl PointerRecomposition for str {

#[inline]
unsafe fn recompose(ptr: Ptr<u8>, data: Self::Components) -> Ptr<Self> {
Some(NonNull::from(std::str::from_utf8_unchecked_mut(
std::slice::from_raw_parts_mut(ptr?.as_ptr(), data),
)))
let ptr = ptr?.as_ptr();
let slice = std::ptr::slice_from_raw_parts_mut(ptr, data);
NonNull::new(slice as *mut str)
}
}
Loading
Loading