Skip to main content

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}