diff --git a/Cargo.toml b/Cargo.toml index b2f498e..0e16487 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,15 @@ thiserror = "1.0" paste = "1.0" quickcheck = "1.0" quickcheck_macros = "1.0" + +[[example]] +name = "readme_example1" +path = "experiments/readme_example1.rs" + +[[example]] +name = "readme_example2" +path = "experiments/readme_example2.rs" + +[[example]] +name = "readme_example3" +path = "experiments/readme_example3.rs" diff --git a/README.md b/README.md new file mode 100644 index 0000000..d76c046 --- /dev/null +++ b/README.md @@ -0,0 +1,185 @@ +# platform-mem + +[![Crates.io](https://img.shields.io/crates/v/platform-mem.svg)](https://crates.io/crates/platform-mem) +[![License](https://img.shields.io/crates/l/platform-mem.svg)](LICENSE) + +A Rust library for low-level memory management with unified interface for allocator-backed and memory-mapped file storage. + +## Overview + +`platform-mem` provides the `RawMem` trait that abstracts over different memory backends: + +- **Allocator-based memory** (`Global`, `System`, `Alloc`) - uses Rust's allocator API +- **Memory-mapped files** (`FileMapped`, `TempFile`) - uses `mmap` for persistent or temporary file-backed storage + +This allows writing generic code that works with any memory backend, making it easy to switch between heap allocation and file-mapped storage. + +## Features + +- **Unified `RawMem` trait** - common interface for growing, shrinking, and accessing memory +- **Type-erased memory** via `ErasedMem` - enables dynamic dispatch with `Box>` +- **Memory-mapped files** - persistent storage with automatic page management +- **Temporary file storage** - anonymous file-backed memory that's cleaned up on drop +- **Safe growth operations** - `grow_filled`, `grow_zeroed`, `grow_from_slice`, and more +- **Thread-safe** - all memory types implement `Send + Sync` + +## Installation + +Add to your `Cargo.toml`: + +```toml +[dependencies] +platform-mem = "0.1" +``` + +**Note:** This crate requires nightly Rust for the `allocator_api` feature. + +```bash +rustup override set nightly +``` + +## Usage + +### Basic Example with Global Allocator + +```rust,ignore +#![feature(allocator_api)] + +use platform_mem::{Global, RawMem}; + +fn main() -> Result<(), platform_mem::Error> { + let mut mem = Global::::new(); + + // Grow memory and fill with value + mem.grow_filled(10, 42)?; + assert_eq!(mem.allocated(), &[42u64; 10]); + + // Grow more from a slice + mem.grow_from_slice(&[1, 2, 3])?; + assert_eq!(mem.allocated().len(), 13); + + // Shrink by 5 elements + mem.shrink(5)?; + assert_eq!(mem.allocated().len(), 8); + + Ok(()) +} +``` + +### Memory-Mapped File Storage + +```rust,ignore +#![feature(allocator_api)] + +use platform_mem::{FileMapped, RawMem}; + +fn main() -> Result<(), platform_mem::Error> { + // Create memory mapped to a file + let mut mem = FileMapped::::from_path("data.bin")?; + + // Data persists across program runs + unsafe { + mem.grow_zeroed(1000)?; + } + + // Modify the memory + mem.allocated_mut()[0] = 123; + + Ok(()) +} +``` + +### Temporary File Storage + +```rust,ignore +#![feature(allocator_api)] + +use platform_mem::{TempFile, RawMem}; + +fn main() -> Result<(), platform_mem::Error> { + // Anonymous temporary file - cleaned up on drop + let mut mem = TempFile::::new()?; + + mem.grow_from_slice(b"hello world")?; + assert_eq!(mem.allocated(), b"hello world"); + + Ok(()) +} +``` + +### Generic Code with `RawMem` + +```rust,ignore +#![feature(allocator_api)] + +use platform_mem::RawMem; + +fn process_data>(mem: &mut M) -> Result<(), platform_mem::Error> { + mem.grow_filled(100, 0)?; + + for (i, slot) in mem.allocated_mut().iter_mut().enumerate() { + *slot = i as u32; + } + + Ok(()) +} +``` + +### Type-Erased Memory with `ErasedMem` + +```rust,ignore +#![feature(allocator_api)] + +use platform_mem::{ErasedMem, Global, RawMem}; + +fn main() { + // Use dynamic dispatch when the memory type isn't known at compile time + let mem: Box + Send + Sync> = + Box::new(Global::::new()); +} +``` + +## API Overview + +### `RawMem` Trait + +The core trait providing memory operations: + +| Method | Description | +|--------|-------------| +| `allocated()` | Returns a slice of the initialized memory | +| `allocated_mut()` | Returns a mutable slice of the initialized memory | +| `grow(addition, fill)` | Grows memory by `addition` elements with custom initialization | +| `shrink(cap)` | Shrinks memory by `cap` elements | +| `grow_filled(cap, value)` | Grows and fills with cloned values | +| `grow_zeroed(cap)` | Grows and zero-initializes (unsafe for non-zeroable types) | +| `grow_from_slice(src)` | Grows and copies from a slice | +| `grow_with(addition, f)` | Grows and initializes with a closure | + +### Memory Types + +| Type | Description | +|------|-------------| +| `Global` | Uses Rust's global allocator | +| `System` | Uses the system allocator | +| `Alloc` | Generic over any `Allocator` | +| `FileMapped` | Memory-mapped file storage | +| `TempFile` | Temporary file-backed memory | + +## Error Handling + +The crate defines an `Error` enum with these variants: + +- `CapacityOverflow` - Requested capacity exceeds `isize::MAX` bytes +- `OverGrow` - Tried to grow more than available space +- `AllocError` - Allocator failed to allocate/reallocate +- `System` - I/O error from file operations + +## License + +This project is released into the public domain under the [Unlicense](LICENSE). + +## Related Projects + +- [doublets-rs](https://github.com/linksplatform/doublets-rs) - Doublet links data structure using this memory library +- [LinksPlatform](https://github.com/linksplatform) - The Links Platform organization diff --git a/experiments/readme_example1.rs b/experiments/readme_example1.rs new file mode 100644 index 0000000..4e9b046 --- /dev/null +++ b/experiments/readme_example1.rs @@ -0,0 +1,22 @@ +#![feature(allocator_api)] + +use platform_mem::{Global, RawMem}; + +fn main() -> Result<(), platform_mem::Error> { + let mut mem = Global::::new(); + + // Grow memory and fill with value + mem.grow_filled(10, 42)?; + assert_eq!(mem.allocated(), &[42u64; 10]); + + // Grow more from a slice + mem.grow_from_slice(&[1, 2, 3])?; + assert_eq!(mem.allocated().len(), 13); + + // Shrink by 5 elements + mem.shrink(5)?; + assert_eq!(mem.allocated().len(), 8); + + println!("Example 1 passed: Basic memory operations work!"); + Ok(()) +} diff --git a/experiments/readme_example2.rs b/experiments/readme_example2.rs new file mode 100644 index 0000000..79fb491 --- /dev/null +++ b/experiments/readme_example2.rs @@ -0,0 +1,14 @@ +#![feature(allocator_api)] + +use platform_mem::{TempFile, RawMem}; + +fn main() -> Result<(), platform_mem::Error> { + // Anonymous temporary file - cleaned up on drop + let mut mem = TempFile::::new()?; + + mem.grow_from_slice(b"hello world")?; + assert_eq!(mem.allocated(), b"hello world"); + + println!("Example 2 passed: TempFile storage works!"); + Ok(()) +} diff --git a/experiments/readme_example3.rs b/experiments/readme_example3.rs new file mode 100644 index 0000000..92026b9 --- /dev/null +++ b/experiments/readme_example3.rs @@ -0,0 +1,26 @@ +#![feature(allocator_api)] + +use platform_mem::RawMem; + +fn process_data>(mem: &mut M) -> Result<(), platform_mem::Error> { + mem.grow_filled(100, 0)?; + + for (i, slot) in mem.allocated_mut().iter_mut().enumerate() { + *slot = i as u32; + } + + Ok(()) +} + +fn main() -> Result<(), platform_mem::Error> { + let mut mem = platform_mem::Global::::new(); + process_data(&mut mem)?; + + // Verify the data + for (i, slot) in mem.allocated().iter().enumerate() { + assert_eq!(*slot, i as u32); + } + + println!("Example 3 passed: Generic code with RawMem works!"); + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index a28b03c..446d51e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,8 @@ #![feature( allocator_api, - unchecked_math, - maybe_uninit_slice, slice_ptr_get, ptr_as_uninit, - inline_const, slice_range, - maybe_uninit_write_slice, unboxed_closures, fn_traits )] diff --git a/src/raw_mem.rs b/src/raw_mem.rs index 66e4138..4cdaade 100644 --- a/src/raw_mem.rs +++ b/src/raw_mem.rs @@ -58,7 +58,7 @@ pub trait RawMem { /// # Safety /// Caller must guarantee that `fill` makes the uninitialized part valid for - /// [`MaybeUninit::slice_assume_init_mut`] + /// [`assume_init_mut`](prim@slice#method.assume_init_mut) /// /// ### Incorrect usage /// ```no_run @@ -128,7 +128,7 @@ pub trait RawMem { } /// # Safety - /// [`Item`] must satisfy [initialization invariant][inv] for [`mem::zeroed`] + /// [`Item`] must satisfy [initialization invariant][inv] for [`std::mem::zeroed`] /// /// [`Item`]: Self::Item /// [inv]: MaybeUninit#initialization-invariant @@ -232,7 +232,7 @@ pub trait RawMem { let Range { start, end } = slice::range(range, ..self.allocated().len()); unsafe { self.grow(end - start, |_, (within, uninit)| { - MaybeUninit::write_slice_cloned(uninit, &within[start..end]); + uninit.write_clone_of_slice(&within[start..end]); }) } } @@ -243,7 +243,7 @@ pub trait RawMem { { unsafe { self.grow(src.len(), |_, (_, uninit)| { - MaybeUninit::write_slice_cloned(uninit, src); + uninit.write_clone_of_slice(src); }) } } @@ -396,9 +396,9 @@ pub mod uninit { // SAFETY: this raw slice will contain only initialized objects // that's why, it is allowed to drop it. unsafe { - ptr::drop_in_place(MaybeUninit::slice_assume_init_mut( - self.slice.get_unchecked_mut(..self.init), - )); + ptr::drop_in_place( + self.slice.get_unchecked_mut(..self.init).assume_init_mut(), + ); } } } diff --git a/src/raw_place.rs b/src/raw_place.rs index 1dfa8a6..646ac8d 100644 --- a/src/raw_place.rs +++ b/src/raw_place.rs @@ -73,7 +73,7 @@ impl RawPlace { self.len = cap; // `len` is same `cap` only if `uninit` was init - MaybeUninit::slice_assume_init_mut(uninit) + uninit.assume_init_mut() } pub fn shrink_to(&mut self, cap: usize) {