├── .gitignore ├── Cargo.toml ├── .travis.yml ├── README.md └── src ├── alloc.rs ├── unique.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *~ 3 | *# 4 | *.o 5 | *.so 6 | *.swp 7 | *.dylib 8 | *.dSYM 9 | *.dll 10 | *.rlib 11 | *.dummy 12 | *.exe 13 | *-test 14 | /doc/ 15 | /target/ 16 | /examples/* 17 | !/examples/*.rs 18 | Cargo.lock 19 | 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "membuf" 4 | version = "0.0.5" 5 | authors = ["Jonathan Reem "] 6 | repository = "https://github.com/reem/rust-membuf.git" 7 | description = "A safe-ish wrapper for allocating and reallocating heap buffers." 8 | readme = "README.md" 9 | license = "MIT" 10 | 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: nightly 3 | sudo: false 4 | 5 | script: 6 | - cargo build 7 | - cargo test 8 | - cargo bench 9 | - cargo doc 10 | 11 | after_success: 12 | - if [ "$TRAVIS_PULL_REQUEST" == false && test == "TRAVIS_BRANCH" == "master" ]; then 13 | - curl https://raw.githubusercontent.com/reem/rust-gh-docs/master/make-docs.sh > docs.sh 14 | - chmod u+x docs.sh 15 | - ./docs.sh reem project-name 16 | 17 | env: 18 | global: 19 | secure: QPYL1XUr4CyK/2DXlsYC1eCpWRpyEiqQSd/FFVR+YdP/rOJ7AyAXQqPhfgjDBQwvc6E2fUiyYjoV/xe1a757DDeZKlgd8Lp20fSDwvNt/Ejx8ueh3h3kuOtgDpIGSKX/l+XC+ltDpzjhh7bowI2/fOEf+kE53jvu9i4PiLnKdlY= 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # membuf 2 | 3 | > A safe-ish wrapper for allocating, reallocating and deallocating heap buffers. 4 | 5 | ## Overview 6 | 7 | 8 | A safe wrapper around a heap allocated buffer of Ts, tracking capacity only. 9 | 10 | MemBuf makes no promises about the actual contents of this memory, that's up 11 | to the user of the structure and can be manipulated using the standard pointer 12 | utilities, accessible through the impl of `Deref` for `MemBuf`. 13 | 14 | You can think of `MemBuf` as an approximation for `Box<[T]>` where the elements 15 | are not guaranteed to be valid/initialized. It is meant to be used as a building 16 | block for other collections, so they do not have to concern themselves with the 17 | minutiae of allocating, reallocating, and deallocating memory. 18 | 19 | However, note that `MemBuf` does not have a destructor, and implements `Copy`, 20 | as a result, it does not implement `Send` or `Sync`, and it is the responsibility 21 | of the user to call `deallocate` if they wish to free memory. 22 | 23 | There is also a `UniqueBuf` which does not implement `Copy`, implements 24 | `Send` and `Sync`, and has a destructor responsible for deallocation. 25 | 26 | ## Usage 27 | 28 | Use the crates.io repository; add this to your `Cargo.toml` along 29 | with the rest of your dependencies: 30 | 31 | ```toml 32 | [dependencies] 33 | membuf = "*" 34 | ``` 35 | 36 | ## Author 37 | 38 | [Jonathan Reem](https://medium.com/@jreem) is the primary author and maintainer 39 | of membuf. 40 | 41 | ## License 42 | 43 | MIT 44 | 45 | -------------------------------------------------------------------------------- /src/alloc.rs: -------------------------------------------------------------------------------- 1 | //! Typed Allocation Utilities 2 | //! 3 | //! Unlike std::rt::heap these check for zero-sized types, capacity overflow, 4 | //! oom etc. and calculate the appropriate size and alignment themselves. 5 | 6 | extern crate alloc; 7 | 8 | use core::nonzero::NonZero; 9 | use std::rt::heap; 10 | use std::mem; 11 | 12 | /// Allocate a new pointer to the heap with space for `cap` `T`s. 13 | pub unsafe fn allocate(cap: NonZero) -> NonZero<*mut T> { 14 | if mem::size_of::() == 0 { return empty() } 15 | 16 | // Allocate 17 | let ptr = heap::allocate(allocation_size::(cap), mem::align_of::()); 18 | 19 | // Check for allocation failure 20 | if ptr.is_null() { alloc::oom() } 21 | 22 | NonZero::new(ptr as *mut T) 23 | } 24 | 25 | /// Reallocate an allocation allocated with `allocate` or a previous call to 26 | /// `reallocate` to be a larger or smaller size. 27 | pub unsafe fn reallocate(ptr: NonZero<*mut T>, 28 | old_cap: NonZero, 29 | new_cap: NonZero) -> NonZero<*mut T> { 30 | if mem::size_of::() == 0 { return empty() } 31 | 32 | let old_size = unchecked_allocation_size::(old_cap); 33 | let new_size = allocation_size::(new_cap); 34 | 35 | // Reallocate 36 | let new = heap::reallocate(*ptr as *mut u8, old_size, new_size, mem::align_of::()); 37 | 38 | // Check for allocation failure 39 | if new.is_null() { 40 | alloc::oom() 41 | } 42 | 43 | NonZero::new(new as *mut T) 44 | } 45 | 46 | /// A zero-sized allocation, appropriate for use with zero sized types. 47 | pub fn empty() -> NonZero<*mut T> { 48 | unsafe { NonZero::new(heap::EMPTY as *mut T) } 49 | } 50 | 51 | /// Deallocate an allocation allocated with `allocate` or `reallocate`. 52 | pub unsafe fn deallocate(ptr: NonZero<*mut T>, cap: NonZero) { 53 | if mem::size_of::() == 0 { return } 54 | 55 | let old_size = unchecked_allocation_size::(cap); 56 | 57 | heap::deallocate(*ptr as *mut u8, old_size, mem::align_of::()) 58 | } 59 | 60 | fn allocation_size(cap: NonZero) -> usize { 61 | mem::size_of::().checked_mul(*cap).expect("Capacity overflow") 62 | } 63 | 64 | fn unchecked_allocation_size(cap: NonZero) -> usize { 65 | mem::size_of::() * (*cap) 66 | } 67 | 68 | -------------------------------------------------------------------------------- /src/unique.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | use MemBuf; 3 | 4 | /// A safe wrapper around a heap allocated buffer of Ts, tracking capacity only. 5 | /// 6 | /// MemBuf makes no promises about the actual contents of this memory, that's up 7 | /// to the user of the structure and can be manipulated using the standard pointer 8 | /// utilities, accessible through the impl of `Deref` for `UniqueBuf`. 9 | /// 10 | /// As a result of this hands-off approach, `UniqueBuf`s destructor does not attempt 11 | /// to drop any of the contained elements; the destructor simply frees the contained 12 | /// memory. 13 | /// 14 | /// You can think of `UniqueBuf` as an approximation for `Box<[T]>` where the elements 15 | /// are not guaranteed to be valid/initialized. It is meant to be used as a building 16 | /// block for other collections, so they do not have to concern themselves with the 17 | /// minutiae of allocating, reallocating, and deallocating memory. 18 | #[derive(Debug, Hash, PartialEq, Eq)] 19 | pub struct UniqueBuf { 20 | inner: MemBuf 21 | } 22 | 23 | unsafe impl Send for UniqueBuf {} 24 | unsafe impl Sync for UniqueBuf {} 25 | 26 | impl UniqueBuf { 27 | /// Create a new, empty UniqueBuf. 28 | /// 29 | /// ``` 30 | /// # use membuf::UniqueBuf; 31 | /// 32 | /// let buffer: UniqueBuf = UniqueBuf::new(); 33 | /// assert_eq!(buffer.capacity(), 0); 34 | /// ``` 35 | pub fn new() -> UniqueBuf { 36 | UniqueBuf { inner: MemBuf::new() } 37 | } 38 | 39 | /// Create a new buffer with space for cap Ts. 40 | /// 41 | /// Unlike `std::rt::heap::allocate`, cap == 0 is allowed. 42 | /// 43 | /// ``` 44 | /// # use membuf::UniqueBuf; 45 | /// 46 | /// let buffer: UniqueBuf = UniqueBuf::allocate(128); 47 | /// assert_eq!(buffer.capacity(), 128); 48 | /// ``` 49 | pub fn allocate(cap: usize) -> UniqueBuf { 50 | UniqueBuf { inner: MemBuf::allocate(cap) } 51 | } 52 | 53 | /// Reallocate this buffer to fit a new number of Ts. 54 | /// 55 | /// Unlike `std::rt::heap::reallocate`, cap == 0 is allowed. 56 | /// 57 | /// ``` 58 | /// # use membuf::UniqueBuf; 59 | /// 60 | /// let mut buffer: UniqueBuf = UniqueBuf::allocate(128); 61 | /// assert_eq!(buffer.capacity(), 128); 62 | /// 63 | /// buffer.reallocate(1024); 64 | /// assert_eq!(buffer.capacity(), 1024); 65 | /// ``` 66 | pub fn reallocate(&mut self, cap: usize) { 67 | unsafe { self.inner.reallocate(cap) } 68 | } 69 | 70 | /// Get the current capacity of the UniqueBuf. 71 | /// 72 | /// ``` 73 | /// # use membuf::UniqueBuf; 74 | /// 75 | /// let buffer: UniqueBuf = UniqueBuf::allocate(128); 76 | /// assert_eq!(buffer.capacity(), 128); 77 | /// ``` 78 | pub fn capacity(&self) -> usize { 79 | self.inner.capacity() 80 | } 81 | 82 | /// Create a UniqueBuf from an existing MemBuf. 83 | /// 84 | /// ``` 85 | /// # use membuf::{MemBuf, UniqueBuf}; 86 | /// 87 | /// let buffer = unsafe { UniqueBuf::from_raw(MemBuf::::allocate(256)) }; 88 | /// assert_eq!(buffer.capacity(), 256); 89 | /// ``` 90 | pub unsafe fn from_raw(buffer: MemBuf) -> UniqueBuf { 91 | UniqueBuf { inner: buffer } 92 | } 93 | } 94 | 95 | impl Drop for UniqueBuf { 96 | fn drop(&mut self) { 97 | unsafe { self.inner.deallocate() } 98 | } 99 | } 100 | 101 | impl Deref for UniqueBuf { 102 | type Target = *mut T; 103 | 104 | fn deref(&self) -> &*mut T { &*self.inner } 105 | } 106 | 107 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(core, nonzero, alloc, oom, heap_api)] 2 | #![cfg_attr(test, deny(warnings))] 3 | #![deny(missing_docs)] 4 | #![allow(raw_pointer_derive)] 5 | 6 | //! # membuf 7 | //! 8 | //! A safe-ish wrapper for allocating and reallocating heap buffers. 9 | //! 10 | 11 | extern crate core; 12 | 13 | pub use unique::UniqueBuf; 14 | 15 | use core::nonzero::NonZero; 16 | use std::ops::Deref; 17 | use std::mem; 18 | 19 | pub mod alloc; 20 | mod unique; 21 | 22 | /// A safe wrapper around a heap allocated buffer of Ts, tracking capacity only. 23 | /// 24 | /// MemBuf makes no promises about the actual contents of this memory, that's up 25 | /// to the user of the structure and can be manipulated using the standard pointer 26 | /// utilities, accessible through the impl of `Deref` for `MemBuf`. 27 | /// 28 | /// You can think of `MemBuf` as an approximation for `Box<[T]>` where the elements 29 | /// are not guaranteed to be valid/initialized. It is meant to be used as a building 30 | /// block for other collections, so they do not have to concern themselves with the 31 | /// minutiae of allocating, reallocating, and deallocating memory. 32 | /// 33 | /// However, note that `MemBuf` does not have a destructor, and implements `Copy`, 34 | /// as a result, it does not implement `Send` or `Sync`, and it is the responsibility 35 | /// of the user to call `deallocate` if they wish to free memory. 36 | #[derive(Debug, Hash, PartialEq, Eq)] 37 | pub struct MemBuf { 38 | buffer: NonZero<*mut T>, 39 | cap: usize 40 | } 41 | 42 | impl Clone for MemBuf { fn clone(&self) -> MemBuf { *self } } 43 | impl Copy for MemBuf {} 44 | 45 | impl MemBuf { 46 | /// Create a new, empty MemBuf. 47 | /// 48 | /// ``` 49 | /// # use membuf::MemBuf; 50 | /// 51 | /// let buffer: MemBuf = MemBuf::new(); 52 | /// assert_eq!(buffer.capacity(), 0); 53 | /// ``` 54 | pub fn new() -> MemBuf { 55 | MemBuf { 56 | buffer: alloc::empty(), 57 | cap: 0 58 | } 59 | } 60 | 61 | /// Create a new buffer with space for cap Ts. 62 | /// 63 | /// Unlike `std::rt::heap::allocate`, cap == 0 is allowed. 64 | /// 65 | /// ``` 66 | /// # use membuf::MemBuf; 67 | /// 68 | /// let buffer: MemBuf = MemBuf::allocate(128); 69 | /// assert_eq!(buffer.capacity(), 128); 70 | /// ``` 71 | pub fn allocate(cap: usize) -> MemBuf { 72 | if cap == 0 { return MemBuf::new() } 73 | 74 | MemBuf { 75 | buffer: unsafe { alloc::allocate(NonZero::new(cap)) }, 76 | cap: cap 77 | } 78 | } 79 | 80 | /// Reallocate this buffer to fit a new number of Ts. 81 | /// 82 | /// Unlike `std::rt::heap::reallocate`, cap == 0 is allowed. 83 | /// 84 | /// ## Safety 85 | /// 86 | /// `reallocate` will invalidate the buffer in all other `MemBuf`s which 87 | /// share the same underlying buffer as this one. As a result, it is possible 88 | /// to cause a double-free by cloning or copying a `MemBuf` and calling 89 | /// `reallocate` from both handles. 90 | /// 91 | /// `UniqueBuf` has a safe `reallocate` implementation, since it cannot be 92 | /// copied or cloned into multiple handles. 93 | /// 94 | /// ``` 95 | /// # use membuf::MemBuf; 96 | /// 97 | /// let mut buffer: MemBuf = MemBuf::allocate(128); 98 | /// assert_eq!(buffer.capacity(), 128); 99 | /// 100 | /// unsafe { buffer.reallocate(1024); } 101 | /// assert_eq!(buffer.capacity(), 1024); 102 | /// ``` 103 | pub unsafe fn reallocate(&mut self, cap: usize) { 104 | if self.cap == 0 || cap == 0 { 105 | mem::replace(self, MemBuf::allocate(cap)).deallocate(); 106 | } else { 107 | // We need to set the capacity to 0 because if the capacity 108 | // overflows unwinding is triggered, which if we don't 109 | // change the capacity would try to free empty(). 110 | let old_cap = mem::replace(&mut self.cap, 0); 111 | let buffer = mem::replace(&mut self.buffer, alloc::empty()); 112 | 113 | self.buffer = alloc::reallocate(buffer, 114 | NonZero::new(old_cap), 115 | NonZero::new(cap)); 116 | self.cap = cap; 117 | } 118 | } 119 | 120 | /// Get the current capacity of the MemBuf. 121 | /// 122 | /// ``` 123 | /// # use membuf::MemBuf; 124 | /// 125 | /// let buffer: MemBuf = MemBuf::allocate(128); 126 | /// assert_eq!(buffer.capacity(), 128); 127 | /// ``` 128 | pub fn capacity(&self) -> usize { 129 | self.cap 130 | } 131 | 132 | /// Deallocate the memory contained within the buffer. 133 | /// 134 | /// The MemBuf will *only* deallocate the contained memory. It will 135 | /// *not* run any destructors on data in that memory. 136 | /// 137 | /// ## Safety 138 | /// 139 | /// `deallocate` will invalidate the buffer in all other `MemBuf`s which 140 | /// share the same underlying buffer as this one. As a result, it is possible 141 | /// to cause a double-free by cloning or copying a `MemBuf` and calling 142 | /// `deallocate` from both handles. 143 | /// 144 | /// `UniqueBuf` has a safe `deallocate` implementation as part of its `Drop` 145 | /// implementation, but cannot be copied or cloned into multiple handles. 146 | /// 147 | pub unsafe fn deallocate(self) { 148 | if self.cap == 0 { return } 149 | alloc::deallocate(self.buffer, NonZero::new(self.cap)); 150 | } 151 | 152 | /// Create a MemBuf from a previously allocated data pointer and a 153 | /// capacity. 154 | pub unsafe fn from_raw(data: NonZero<*mut T>, capacity: usize) -> MemBuf { 155 | MemBuf { 156 | buffer: data, 157 | cap: capacity 158 | } 159 | } 160 | } 161 | 162 | impl Deref for MemBuf { 163 | type Target = *mut T; 164 | 165 | fn deref(&self) -> &*mut T { 166 | &*self.buffer 167 | } 168 | } 169 | 170 | #[cfg(test)] 171 | mod test { 172 | use std::ptr; 173 | use alloc::empty; 174 | use MemBuf; 175 | 176 | #[test] 177 | fn test_empty() { 178 | let buffer: MemBuf = MemBuf::new(); 179 | assert_eq!(buffer.cap, 0); 180 | assert_eq!(buffer.buffer, empty()); 181 | } 182 | 183 | #[test] 184 | fn test_allocate() { 185 | let buffer: MemBuf = MemBuf::allocate(8); 186 | 187 | assert_eq!(buffer.cap, 8); 188 | 189 | unsafe { 190 | ptr::write(buffer.offset(0), 8); 191 | ptr::write(buffer.offset(1), 4); 192 | ptr::write(buffer.offset(3), 5); 193 | ptr::write(buffer.offset(5), 3); 194 | ptr::write(buffer.offset(7), 6); 195 | 196 | assert_eq!(ptr::read(buffer.offset(0)), 8); 197 | assert_eq!(ptr::read(buffer.offset(1)), 4); 198 | assert_eq!(ptr::read(buffer.offset(3)), 5); 199 | assert_eq!(ptr::read(buffer.offset(5)), 3); 200 | assert_eq!(ptr::read(buffer.offset(7)), 6); 201 | }; 202 | 203 | // Try a large buffer 204 | let buffer: MemBuf = MemBuf::allocate(1024 * 1024); 205 | 206 | unsafe { 207 | ptr::write(buffer.offset(1024 * 1024 - 1), 12); 208 | assert_eq!(ptr::read(buffer.offset(1024 * 1024 - 1)), 12); 209 | }; 210 | } 211 | 212 | #[test] 213 | fn test_reallocate() { 214 | let mut buffer: MemBuf = MemBuf::allocate(8); 215 | assert_eq!(buffer.cap, 8); 216 | 217 | unsafe { buffer.reallocate(16); } 218 | assert_eq!(buffer.cap, 16); 219 | 220 | unsafe { 221 | // Put some data in the buffer 222 | ptr::write(buffer.offset(0), 8); 223 | ptr::write(buffer.offset(1), 4); 224 | ptr::write(buffer.offset(5), 3); 225 | ptr::write(buffer.offset(7), 6); 226 | }; 227 | 228 | // Allocate so in-place fails. 229 | let _: MemBuf = MemBuf::allocate(128); 230 | 231 | unsafe { buffer.reallocate(32); } 232 | 233 | unsafe { 234 | // Ensure the data is still there. 235 | assert_eq!(ptr::read(buffer.offset(0)), 8); 236 | assert_eq!(ptr::read(buffer.offset(1)), 4); 237 | assert_eq!(ptr::read(buffer.offset(5)), 3); 238 | assert_eq!(ptr::read(buffer.offset(7)), 6); 239 | }; 240 | } 241 | 242 | #[test] 243 | #[should_panic = "Capacity overflow."] 244 | fn test_allocate_capacity_overflow() { 245 | let _: MemBuf = MemBuf::allocate(10_000_000_000_000_000_000); 246 | } 247 | 248 | #[test] 249 | #[should_panic = "Capacity overflow."] 250 | fn test_fresh_reallocate_capacity_overflow() { 251 | let mut buffer: MemBuf = MemBuf::new(); 252 | unsafe { buffer.reallocate(10_000_000_000_000_000_000); } 253 | } 254 | 255 | #[test] 256 | #[should_panic = "Capacity overflow."] 257 | fn test_reallocate_capacity_overflow() { 258 | let mut buffer: MemBuf = MemBuf::allocate(128); 259 | unsafe { buffer.reallocate(10_000_000_000_000_000_000); } 260 | } 261 | } 262 | 263 | --------------------------------------------------------------------------------