platform_mem/lib.rs
1//! Low-level memory management with pluggable backends.
2//!
3//! `platform-mem` provides a unified [`RawMem`] trait that abstracts over
4//! different memory storage strategies: heap allocators, memory-mapped files,
5//! and temporary files. Code written against [`RawMem`] works with any backend.
6//!
7//! # Backends
8//!
9//! | Type | Storage | Use case |
10//! |------|---------|----------|
11//! | [`Global<T>`] | Rust global allocator | General-purpose in-memory storage |
12//! | [`System<T>`] | System allocator | When you need the OS allocator specifically |
13//! | [`Alloc<T, A>`] | Any [`Allocator`](allocator_api2::alloc::Allocator) | Custom allocator strategies |
14//! | [`FileMapped<T>`] | Memory-mapped file | Persistent storage, large datasets |
15//! | [`TempFile<T>`] | Temporary mmap file | Anonymous storage cleaned on drop |
16//! | [`AsyncFileMem<T>`](async_mem::AsyncFileMem) | Async mmap via I/O thread | Non-blocking file-backed storage (requires `async` feature) |
17//!
18//! # Quick start
19//!
20//! ```
21//! use platform_mem::{Global, RawMem};
22//!
23//! let mut mem = Global::<u64>::new();
24//! mem.grow_filled(3, 42).unwrap();
25//! assert_eq!(mem.allocated(), &[42, 42, 42]);
26//!
27//! mem.grow_from_slice(&[1, 2, 3]).unwrap();
28//! assert_eq!(mem.allocated(), &[42, 42, 42, 1, 2, 3]);
29//!
30//! mem.shrink(2).unwrap();
31//! assert_eq!(mem.allocated(), &[42, 42, 42, 1]);
32//! ```
33//!
34//! # Type erasure
35//!
36//! Use [`ErasedMem`] to work with heterogeneous memory backends via dynamic dispatch:
37//!
38//! ```
39//! use platform_mem::{Global, ErasedMem, RawMem};
40//!
41//! fn use_any_mem(mem: &mut Box<dyn ErasedMem<Item = u64>>) {
42//! mem.grow_filled(5, 0).unwrap();
43//! }
44//!
45//! let mut mem: Box<dyn ErasedMem<Item = u64>> = Box::new(Global::<u64>::new());
46//! use_any_mem(&mut mem);
47//! assert_eq!(mem.allocated().len(), 5);
48//! ```
49//!
50//! # Features
51//!
52//! - **`async`** — enables [`AsyncFileMem`] for non-blocking
53//! file-backed memory via a dedicated I/O thread (requires tokio).
54
55// special lint
56#![cfg_attr(not(test), forbid(clippy::unwrap_used))]
57// rust compiler lints
58#![deny(unused_must_use)]
59#![warn(missing_docs, missing_debug_implementations)]
60
61mod alloc;
62mod file_mapped;
63/// Core trait definitions, error types, and `MaybeUninit` helpers.
64pub mod raw_mem;
65mod raw_place;
66mod utils;
67
68/// Async memory module providing [`AsyncFileMem`].
69///
70/// Requires the `async` feature flag.
71#[cfg(feature = "async")]
72pub mod async_mem;
73
74pub(crate) use raw_place::RawPlace;
75pub use {
76 alloc::Alloc,
77 file_mapped::FileMapped,
78 raw_mem::{ErasedMem, Error, RawMem, Result},
79};
80
81#[cfg(feature = "async")]
82pub use async_mem::AsyncFileMem;
83
84fn _assertion() {
85 fn assert_sync_send<T: Sync + Send>() {}
86
87 assert_sync_send::<FileMapped<()>>();
88 assert_sync_send::<Alloc<(), allocator_api2::alloc::Global>>();
89}
90
91macro_rules! delegate_memory {
92 ($($(#[$attr:meta])* $me:ident<$param:ident>($inner:ty) { $($body:tt)* } )*) => {$(
93 $(#[$attr])*
94 pub struct $me<$param>($inner);
95
96 impl<$param> $me<$param> {
97 $($body)*
98 }
99
100 const _: () = {
101 use std::{
102 mem::MaybeUninit,
103 fmt::{self, Formatter},
104 };
105
106 impl<$param> RawMem for $me<$param> {
107 type Item = $param;
108
109 fn allocated(&self) -> &[Self::Item] {
110 self.0.allocated()
111 }
112
113 fn allocated_mut(&mut self) -> &mut [Self::Item] {
114 self.0.allocated_mut()
115 }
116
117 unsafe fn grow(
118 &mut self,
119 addition: usize,
120 fill: impl FnOnce(usize, (&mut [Self::Item], &mut [MaybeUninit<Self::Item>])),
121 ) -> Result<&mut [Self::Item]> { unsafe {
122 self.0.grow(addition, fill)
123 }}
124
125 fn shrink(&mut self, cap: usize) -> Result<()> {
126 self.0.shrink(cap)
127 }
128
129 fn size_hint(&self) -> Option<usize> {
130 self.0.size_hint()
131 }
132 }
133
134 impl<T> fmt::Debug for $me<$param> {
135 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
136 f.debug_tuple(stringify!($me)).field(&self.0).finish()
137 }
138 }
139
140 };
141 )*};
142}
143
144use allocator_api2::alloc::Global as GlobalAlloc;
145use std::{alloc::System as SystemAlloc, fs::File, io, path::Path};
146
147delegate_memory! {
148 /// Memory backed by the Rust global allocator.
149 ///
150 /// This is the most common backend for in-memory storage.
151 ///
152 /// ```
153 /// use platform_mem::{Global, RawMem};
154 ///
155 /// let mut mem = Global::<u64>::new();
156 /// mem.grow_filled(10, 0).unwrap();
157 /// assert_eq!(mem.allocated().len(), 10);
158 /// ```
159 #[doc(alias = "GlobalAlloc")]
160 Global<T>(Alloc<T, GlobalAlloc>) {
161 /// Creates a new empty `Global` memory.
162 pub const fn new() -> Self {
163 Self(Alloc::new(GlobalAlloc))
164 }
165 }
166
167 /// Memory backed by the system allocator.
168 #[doc(alias = "SystemAlloc")]
169 System<T>(Alloc<T, SystemAlloc>) {
170 /// Creates a new empty `System` memory.
171 pub const fn new() -> Self {
172 Self(Alloc::new(SystemAlloc))
173 }
174 }
175
176 /// Temporary file-backed memory-mapped storage.
177 ///
178 /// Data is stored in an anonymous temporary file that is automatically
179 /// cleaned up when the `TempFile` is dropped.
180 TempFile<T>(FileMapped<T>) {
181 /// Creates a new temporary file-backed memory.
182 pub fn new() -> io::Result<Self> {
183 Self::from_temp(tempfile::tempfile())
184 }
185
186 /// Creates a new temporary file-backed memory in the specified directory.
187 pub fn new_in<P: AsRef<Path>>(path: P) -> io::Result<Self> {
188 Self::from_temp(tempfile::tempfile_in(path))
189 }
190
191 fn from_temp(file: io::Result<File>) -> io::Result<Self> {
192 file.and_then(FileMapped::new).map(Self)
193 }
194 }
195}
196
197impl<T> Default for Global<T> {
198 fn default() -> Self {
199 Self::new()
200 }
201}
202
203impl<T> Default for System<T> {
204 fn default() -> Self {
205 Self::new()
206 }
207}
208
209fn _is_raw_mem() {
210 fn check<T: RawMem>() {}
211
212 check::<Box<dyn ErasedMem<Item = ()>>>();
213 check::<Box<dyn ErasedMem<Item = ()> + Sync>>();
214 check::<Box<dyn ErasedMem<Item = ()> + Sync + Send>>();
215
216 fn elie() -> Box<Global<()>> {
217 todo!()
218 }
219
220 let _: Box<dyn ErasedMem<Item = ()>> = elie();
221 let _: Box<dyn ErasedMem<Item = ()> + Sync> = elie();
222 let _: Box<dyn ErasedMem<Item = ()> + Sync + Send> = elie();
223}