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
12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
185 changes: 185 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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<T, A>`) - 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<dyn ErasedMem<Item = T>>`
- **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::<u64>::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::<u64>::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::<u8>::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<M: RawMem<Item = u32>>(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<dyn ErasedMem<Item = u64> + Send + Sync> =
Box::new(Global::<u64>::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<T>` | Uses Rust's global allocator |
| `System<T>` | Uses the system allocator |
| `Alloc<T, A>` | Generic over any `Allocator` |
| `FileMapped<T>` | Memory-mapped file storage |
| `TempFile<T>` | 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
22 changes: 22 additions & 0 deletions experiments/readme_example1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#![feature(allocator_api)]

use platform_mem::{Global, RawMem};

fn main() -> Result<(), platform_mem::Error> {
let mut mem = Global::<u64>::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(())
}
14 changes: 14 additions & 0 deletions experiments/readme_example2.rs
Original file line number Diff line number Diff line change
@@ -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::<u8>::new()?;

mem.grow_from_slice(b"hello world")?;
assert_eq!(mem.allocated(), b"hello world");

println!("Example 2 passed: TempFile storage works!");
Ok(())
}
26 changes: 26 additions & 0 deletions experiments/readme_example3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#![feature(allocator_api)]

use platform_mem::RawMem;

fn process_data<M: RawMem<Item = u32>>(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::<u32>::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(())
}
4 changes: 0 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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
)]
Expand Down
14 changes: 7 additions & 7 deletions src/raw_mem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]);
})
}
}
Expand All @@ -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);
})
}
}
Expand Down Expand Up @@ -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(),
);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/raw_place.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ impl<T> RawPlace<T> {

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) {
Expand Down