├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── benches └── benchmark.rs ├── rust-toolchain └── src ├── fuse.rs ├── fuse ├── iter.rs └── test.rs └── lib.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - uses: actions-rs/toolchain@v1 20 | with: 21 | toolchain: nightly 22 | components: clippy, miri 23 | override: true 24 | - name: Build 25 | run: cargo build --verbose 26 | - name: Run clippy 27 | run: cargo clippy 28 | - name: Run tests 29 | run: cargo test --verbose 30 | - name: Run miri 31 | run: cargo miri test --verbose 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | *.py 4 | *.png 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fusebox" 3 | version = "0.8.3" 4 | edition = "2021" 5 | description = "Mostly safe and sound append-only collection of trait objects." 6 | repository = "https://github.com/JohnDowson/fusebox" 7 | license-file = "LICENSE" 8 | categories = ["data-structures"] 9 | keywords = ["trait", "dynamic", "collection", "heterogeneous"] 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [features] 14 | # bench = ["dep:criterion", "dep:rand", "dep:pprof", "dep:bumpalo"] 15 | bench = ["dep:criterion", "dep:pprof", "dep:bumpalo"] 16 | 17 | [dependencies] 18 | criterion = { version = "0.4.0", features = ["html_reports"], optional = true } 19 | rand = { version = "0.8.5", optional = false } 20 | pprof = { version = "0.11.0", features = [ 21 | "flamegraph", 22 | "criterion", 23 | ], optional = true } 24 | bumpalo = { version = "3.11.1", features = ["boxed"], optional = true } 25 | 26 | # [dependencies] 27 | # criterion = { version = "0.4.0", features = ["html_reports"] } 28 | # rand = { version = "0.8.5" } 29 | # pprof = { version = "0.11.0", features = ["flamegraph", "criterion"] } 30 | # bumpalo = { version = "3.11.1", features = ["boxed"] } 31 | 32 | [[bench]] 33 | name = "benchmark" 34 | harness = false 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Ivan Chinenov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fusebox [![crates.io](https://img.shields.io/crates/v/fusebox.svg)](https://crates.io/crates/fusebox) [![CI status](https://img.shields.io/github/actions/workflow/status/JohnDowson/fusebox/rust.yml)](https://github.com/JohnDowson/fusebox/actions) 2 | Mostly safe and sound append-only collection of trait objects. 3 | 4 | ## Why? 5 | This avoids extra indirection of `Vec>`, which might matter for you. 6 | I personally use it in [pcmg](https://github.com/JohnDowson/pcmg) audio synthesizer for fusing together multiple filters and oscillators. 7 | 8 | # Changelog 9 | ## 0.8.3 10 | - Alignment bug in reallocation logic (#5) 11 | 12 | ## 0.8.2 13 | - Fix bug in reallocation logic (#4) 14 | 15 | ## 0.8.0 16 | - `push_unsafe` removed from public API 17 | - `push` no longer requires `T: Send`, instead `Send` and `Sync` are implemented for `FuseBox` depending on wether `Dyn` is 18 | 19 | ## 0.7.0 20 | - Improved iteration performance by using two pointer technique 21 | 22 | ## 0.6.0 23 | - Performance improvements 24 | - Soundness fixes 25 | 26 | ## 0.5.0 27 | - Use `Unsize` instead of `AsDyn` marker trait, making safe push for foreign types possible 28 | 29 | ## 0.4.0 30 | - Removed `Sz` parameter from `FuseBox` 31 | - `FuseBox` now supports truly random access 32 | 33 | ## 0.3.0 34 | - Added `Size` to restrict `Sz` to valid unsigned integers 35 | 36 | ## 0.2.0 37 | - Added `AsDyn` to make safe `push` possible. 38 | - Fixed pushed values not being dropped when `FuseBox` is dropped 39 | 40 | ## 0.1.0 41 | Initial release 42 | -------------------------------------------------------------------------------- /benches/benchmark.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "bench")] 2 | use bumpalo::Bump; 3 | #[cfg(feature = "bench")] 4 | use criterion::{black_box, criterion_group, criterion_main, Criterion, PlottingBackend}; 5 | #[cfg(feature = "bench")] 6 | use fusebox::{inline_meta, FuseBox}; 7 | #[cfg(feature = "bench")] 8 | use pprof::criterion::{Output, PProfProfiler}; 9 | #[cfg(feature = "bench")] 10 | use rand::prelude::*; 11 | 12 | #[cfg(feature = "bench")] 13 | const SEED: u64 = 420; 14 | 15 | #[cfg(feature = "bench")] 16 | macro_rules! calc_struct { 17 | ($n:ident, $op:tt; $($f:ident),*) => { 18 | #[derive(Clone, Copy)] 19 | struct $n { 20 | result: f32, 21 | $($f: f32,)* 22 | } 23 | 24 | impl $n { 25 | fn new(r: &mut StdRng) -> Self { 26 | Self { 27 | result: r.gen(), 28 | $($f: 0.0,)* 29 | } 30 | } 31 | fn boxed(r: &mut StdRng) -> Box { 32 | let this = Self::new(r); 33 | Box::new(this) 34 | } 35 | fn bump<'b>(r: &mut StdRng, b: &'b Bump) -> &'b mut dyn Calculation { 36 | let this = Self::new(r); 37 | b.alloc(this) 38 | } 39 | } 40 | 41 | impl Calculation for $n { 42 | fn calculate(&mut self) { 43 | self.result = $(self.$f $op)* 1.0; 44 | } 45 | fn get_result(&self) -> f32 { 46 | self.result 47 | } 48 | } 49 | }; 50 | } 51 | 52 | #[cfg(feature = "bench")] 53 | trait Calculation { 54 | fn calculate(&mut self); 55 | fn get_result(&self) -> f32; 56 | } 57 | 58 | #[cfg(feature = "bench")] 59 | fn prepare_vec_bumpalo<'b>(n: usize, b: &'b Bump) -> Vec<&'b mut dyn Calculation> { 60 | let mut r = StdRng::seed_from_u64(SEED); 61 | (0..n) 62 | .map(|_| { 63 | let u = r.gen_range(0..=5); 64 | match u { 65 | 0 => A::bump(&mut r, b), 66 | 1 => B::bump(&mut r, b), 67 | 2 => C::bump(&mut r, b), 68 | 3 => D::bump(&mut r, b), 69 | 4 => E::bump(&mut r, b), 70 | 5 => F::bump(&mut r, b), 71 | _ => unreachable!(), 72 | } 73 | }) 74 | .collect() 75 | } 76 | 77 | #[cfg(feature = "bench")] 78 | fn prepare_vec(n: usize) -> Vec> { 79 | let mut r = StdRng::seed_from_u64(SEED); 80 | (0..n) 81 | .map(|_| { 82 | let u = r.gen_range(0..=5); 83 | match u { 84 | 0 => A::boxed(&mut r), 85 | 1 => B::boxed(&mut r), 86 | 2 => C::boxed(&mut r), 87 | 3 => D::boxed(&mut r), 88 | 4 => E::boxed(&mut r), 89 | 5 => F::boxed(&mut r), 90 | _ => unreachable!(), 91 | } 92 | }) 93 | .collect() 94 | } 95 | 96 | #[cfg(feature = "bench")] 97 | fn prepare_fused(n: usize) -> FuseBox { 98 | let mut fused = FuseBox::default(); 99 | let mut r = StdRng::seed_from_u64(SEED); 100 | for _ in 0..n { 101 | let u = r.gen_range(0..=5); 102 | match u { 103 | 0 => fused.push(A::new(&mut r)), 104 | 1 => fused.push(B::new(&mut r)), 105 | 2 => fused.push(C::new(&mut r)), 106 | 3 => fused.push(D::new(&mut r)), 107 | 4 => fused.push(E::new(&mut r)), 108 | 5 => fused.push(F::new(&mut r)), 109 | _ => unreachable!(), 110 | } 111 | } 112 | fused 113 | } 114 | 115 | #[cfg(feature = "bench")] 116 | fn prepare_inline_meta_fused(n: usize) -> inline_meta::FuseBox { 117 | let mut fused = inline_meta::FuseBox::default(); 118 | let mut r = StdRng::seed_from_u64(SEED); 119 | for _ in 0..n { 120 | let u = r.gen_range(0..=5); 121 | match u { 122 | 0 => fused.push(A::new(&mut r)), 123 | 1 => fused.push(B::new(&mut r)), 124 | 2 => fused.push(C::new(&mut r)), 125 | 3 => fused.push(D::new(&mut r)), 126 | 4 => fused.push(E::new(&mut r)), 127 | 5 => fused.push(F::new(&mut r)), 128 | _ => unreachable!(), 129 | } 130 | } 131 | fused 132 | } 133 | 134 | #[cfg(feature = "bench")] 135 | calc_struct!(A, *; a, b, c, d, e, f); 136 | #[cfg(feature = "bench")] 137 | calc_struct!(B, *; a, b, c, d, e); 138 | #[cfg(feature = "bench")] 139 | calc_struct!(C, *; a, b, c, d); 140 | #[cfg(feature = "bench")] 141 | calc_struct!(D, *; a, b, c); 142 | #[cfg(feature = "bench")] 143 | calc_struct!(E, *; a, b); 144 | #[cfg(feature = "bench")] 145 | calc_struct!(F, *; a); 146 | 147 | #[cfg(feature = "bench")] 148 | fn iteration(c: &mut Criterion) { 149 | let mut g = c.benchmark_group("Linear access"); 150 | for n in (0..=512).step_by(64).skip(1) { 151 | g.bench_with_input(format!("Vec_n{n}"), &n, |b, &n| { 152 | let mut v = black_box(prepare_vec(n)); 153 | 154 | b.iter(|| { 155 | for v in v.iter_mut() { 156 | v.calculate() 157 | } 158 | for v in v.iter() { 159 | black_box(v.get_result()); 160 | } 161 | }) 162 | }); 163 | g.bench_with_input(format!("Vec_bumpalo_n{n}"), &n, |b, &n| { 164 | let bump = Bump::new(); 165 | let mut v = black_box(prepare_vec_bumpalo(n, &bump)); 166 | 167 | b.iter(|| { 168 | for v in v.iter_mut() { 169 | v.calculate() 170 | } 171 | for v in v.iter() { 172 | black_box(v.get_result()); 173 | } 174 | }) 175 | }); 176 | g.bench_with_input(format!("FuseBox_n{n}"), &n, |b, &n| { 177 | let mut f = prepare_fused(n); 178 | 179 | b.iter(|| { 180 | for v in f.iter_mut() { 181 | v.calculate() 182 | } 183 | for v in f.iter() { 184 | black_box(v.get_result()); 185 | } 186 | }) 187 | }); 188 | } 189 | g.finish(); 190 | } 191 | 192 | #[cfg(feature = "bench")] 193 | fn random_access(c: &mut Criterion) { 194 | let mut g = c.benchmark_group("Random access"); 195 | for n in (0..=512).step_by(64).skip(1) { 196 | g.bench_with_input(format!("Vec_n{n}"), &n, |b, &n| { 197 | let mut r = StdRng::from_rng(thread_rng()).unwrap(); 198 | let mut v = black_box(prepare_vec(n)); 199 | 200 | b.iter(|| { 201 | let n = r.gen_range(0..n); 202 | let v = &mut v[n]; 203 | v.calculate(); 204 | 205 | black_box(v.get_result()); 206 | }) 207 | }); 208 | g.bench_with_input(format!("Vec_bumpalo_n{n}"), &n, |b, &n| { 209 | let mut r = StdRng::from_rng(thread_rng()).unwrap(); 210 | let bump = Bump::new(); 211 | let mut v = black_box(prepare_vec_bumpalo(n, &bump)); 212 | 213 | b.iter(|| { 214 | let n = r.gen_range(0..n); 215 | let v = &mut v[n]; 216 | v.calculate(); 217 | 218 | black_box(v.get_result()); 219 | }) 220 | }); 221 | g.bench_with_input(format!("FuseBox_n{n}"), &n, |b, &n| { 222 | let mut r = StdRng::from_rng(thread_rng()).unwrap(); 223 | let mut f = black_box(prepare_fused(n)); 224 | 225 | b.iter(|| { 226 | let n = r.gen_range(0..n); 227 | let v = &mut f[n]; 228 | v.calculate(); 229 | v.get_result(); 230 | }) 231 | }); 232 | } 233 | g.finish(); 234 | } 235 | 236 | #[cfg(feature = "bench")] 237 | fn config(pprof: bool) -> Criterion { 238 | let c = Criterion::default() 239 | .sample_size(100) 240 | .plotting_backend(PlottingBackend::Gnuplot) 241 | .with_plots(); 242 | if pprof { 243 | c.with_profiler(PProfProfiler::new(500, Output::Flamegraph(None))) 244 | } else { 245 | c 246 | } 247 | } 248 | 249 | #[cfg(feature = "bench")] 250 | criterion_group!(name = benches; 251 | config = config(false); 252 | targets = iteration, random_access); 253 | #[cfg(feature = "bench")] 254 | criterion_main!(benches); 255 | 256 | #[cfg(not(feature = "bench"))] 257 | fn main() {} 258 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly 2 | -------------------------------------------------------------------------------- /src/fuse.rs: -------------------------------------------------------------------------------- 1 | use iter::{Iter, IterMut}; 2 | use std::{ 3 | alloc::{alloc, dealloc, Layout}, 4 | marker::Unsize, 5 | ops::{Index, IndexMut}, 6 | ptr::{self, drop_in_place, NonNull, Pointee}, 7 | }; 8 | 9 | pub mod iter; 10 | 11 | #[cfg(test)] 12 | mod test; 13 | 14 | #[derive(Clone, Copy)] 15 | struct Header 16 | where 17 | Dyn: ?Sized, 18 | { 19 | offset: usize, 20 | meta: ::Metadata, 21 | } 22 | 23 | /// Contigous type-erased append-only vector 24 | /// 25 | /// `Dyn` shall be `dyn Trait` 26 | pub struct FuseBox 27 | where 28 | Dyn: ?Sized, 29 | { 30 | headers: Vec>, 31 | inner: NonNull, 32 | last_size: usize, 33 | max_align: usize, 34 | len_bytes: usize, 35 | cap_bytes: usize, 36 | } 37 | 38 | impl Default for FuseBox 39 | where 40 | Dyn: ?Sized, 41 | { 42 | fn default() -> Self { 43 | Self::new() 44 | } 45 | } 46 | 47 | impl Drop for FuseBox 48 | where 49 | Dyn: ?Sized, 50 | { 51 | fn drop(&mut self) { 52 | if self.cap_bytes != 0 { 53 | // Safety: 54 | // inner guaranteed to be valid here 55 | // values are guaranteed to be aligned 56 | unsafe { 57 | for val in self.iter_mut() { 58 | drop_in_place(val); 59 | } 60 | dealloc( 61 | self.inner.as_ptr(), 62 | Layout::from_size_align_unchecked(self.cap_bytes, self.max_align), 63 | ); 64 | } 65 | } 66 | } 67 | } 68 | 69 | unsafe impl Send for FuseBox 70 | where 71 | Dyn: ?Sized, 72 | Dyn: Send, 73 | { 74 | } 75 | 76 | unsafe impl Sync for FuseBox 77 | where 78 | Dyn: ?Sized, 79 | Dyn: Sync, 80 | { 81 | } 82 | 83 | impl FuseBox 84 | where 85 | Dyn: ?Sized, 86 | { 87 | #[must_use] 88 | /// Creates a new [`FuseBox`]. 89 | pub fn new() -> Self { 90 | Self { 91 | headers: Vec::new(), 92 | inner: std::ptr::NonNull::dangling(), 93 | last_size: 0, 94 | max_align: 0, 95 | len_bytes: 0, 96 | cap_bytes: 0, 97 | } 98 | } 99 | 100 | #[must_use] 101 | #[inline] 102 | /// Returns the length of this [`FuseBox`] in items. 103 | pub fn len(&self) -> usize { 104 | self.headers.len() 105 | } 106 | 107 | #[must_use] 108 | #[inline] 109 | pub fn is_empty(&self) -> bool { 110 | self.len() == 0 111 | } 112 | 113 | #[inline] 114 | fn realloc(&mut self, min_layout: Layout) { 115 | if self.cap_bytes == 0 { 116 | self.max_align = min_layout.align(); 117 | unsafe { 118 | self.cap_bytes = min_layout.pad_to_align().size(); 119 | let new = alloc(min_layout); 120 | if new.is_null() { 121 | panic!( 122 | "Failed to allocate memory for {}", 123 | std::any::type_name::() 124 | ) 125 | } 126 | self.inner = NonNull::new_unchecked(new); 127 | } 128 | return; 129 | } 130 | 131 | let old = self.inner; 132 | let old_layout = 133 | unsafe { Layout::from_size_align_unchecked(self.cap_bytes, self.max_align) }; 134 | if self.max_align < min_layout.align() { 135 | self.max_align = min_layout.align(); 136 | } 137 | let size = if self.cap_bytes == 0 { 138 | min_layout.size() 139 | } else { 140 | self.cap_bytes 141 | .checked_mul(2) 142 | .and_then(|s| s.checked_add(min_layout.size())) 143 | .expect("New capacity overflowed usize") 144 | }; 145 | unsafe { 146 | let layout = Layout::from_size_align_unchecked(size, self.max_align).pad_to_align(); 147 | self.cap_bytes = layout.size(); 148 | let new = alloc(layout); 149 | std::ptr::copy(old.as_ptr(), new, self.len_bytes); 150 | self.inner = NonNull::new_unchecked(new); 151 | dealloc(old.as_ptr(), old_layout); 152 | } 153 | } 154 | 155 | #[inline] 156 | unsafe fn push_unsafe(&mut self, v: T) 157 | where 158 | T: 'static, 159 | T: Unsize, 160 | { 161 | let as_dyn: &Dyn = &v; 162 | let meta = ptr::metadata(as_dyn); 163 | let layout = Layout::new::(); 164 | let header = self.make_header(layout, meta); 165 | let offset = header.offset; 166 | 167 | if layout.size() == 0 && layout.align() <= 1 { 168 | // Safety: offset guaranteed to be in-bounds 169 | unsafe { self.inner.as_ptr().add(offset).cast::().write(v) } 170 | self.headers.push(header); 171 | } else { 172 | if self.cap_bytes.saturating_sub(offset) < layout.size() 173 | || layout.align() > self.max_align 174 | { 175 | self.realloc(layout); 176 | } 177 | 178 | unsafe { self.inner.as_ptr().add(offset).cast::().write(v) } 179 | self.headers.push(header); 180 | } 181 | self.last_size = layout.size(); 182 | self.len_bytes = offset + layout.size(); 183 | } 184 | 185 | #[inline] 186 | fn make_header(&mut self, layout: Layout, meta: ::Metadata) -> Header { 187 | if self.is_empty() { 188 | Header { offset: 0, meta } 189 | } else { 190 | let Header { offset, meta: _ } = self.headers[self.len() - 1]; 191 | let offset = round_up(offset + self.last_size, layout.align()); 192 | Header { offset, meta } 193 | } 194 | } 195 | 196 | #[inline] 197 | /// Appends an element to the vector. 198 | pub fn push(&mut self, v: T) 199 | where 200 | T: 'static, 201 | T: Unsize, 202 | Dyn: 'static, 203 | { 204 | unsafe { self.push_unsafe(v) } 205 | } 206 | 207 | #[inline] 208 | pub(crate) unsafe fn get_raw(&self, n: usize) -> *mut Dyn { 209 | let Header { offset, meta } = self.headers[n]; 210 | unsafe { 211 | let ptr = self.inner.as_ptr().add(offset).cast(); 212 | ptr::from_raw_parts_mut::(ptr, meta) 213 | } 214 | } 215 | 216 | #[inline] 217 | /// Retrieves `&mut Dyn` from [`FuseBox`]. 218 | pub fn get_mut(&mut self, n: usize) -> Option<&mut Dyn> { 219 | if self.len() <= n { 220 | return None; 221 | } 222 | unsafe { Some(&mut *self.get_raw(n)) } 223 | } 224 | 225 | #[inline] 226 | #[must_use] 227 | /// Retrieves `&Dyn` from [`FuseBox`]. 228 | pub fn get(&self, n: usize) -> Option<&Dyn> { 229 | if self.len() <= n { 230 | return None; 231 | } 232 | unsafe { Some(&*self.get_raw(n)) } 233 | } 234 | 235 | #[must_use] 236 | /// Returns an iterator over `&Dyn` stored in this [`FuseBox`] 237 | pub fn iter(&'_ self) -> Iter<'_, Dyn> { 238 | Iter::new(self) 239 | } 240 | 241 | #[must_use] 242 | /// Returns an iterator over `&mut Dyn` stored in this [`FuseBox`]. 243 | pub fn iter_mut(&'_ mut self) -> IterMut<'_, Dyn> { 244 | IterMut::new(self) 245 | } 246 | } 247 | 248 | impl Index for FuseBox 249 | where 250 | Dyn: ?Sized, 251 | { 252 | type Output = Dyn; 253 | 254 | #[inline] 255 | fn index(&self, index: usize) -> &Self::Output { 256 | assert!(index < self.len()); 257 | unsafe { &*self.get_raw(index) } 258 | } 259 | } 260 | 261 | impl IndexMut for FuseBox 262 | where 263 | Dyn: ?Sized, 264 | { 265 | #[inline] 266 | fn index_mut(&mut self, index: usize) -> &mut Self::Output { 267 | assert!(index < self.len()); 268 | unsafe { &mut *self.get_raw(index) } 269 | } 270 | } 271 | 272 | fn round_up(n: usize, m: usize) -> usize { 273 | if m == 0 { 274 | n 275 | } else { 276 | let rem = n % m; 277 | if rem == 0 { 278 | n 279 | } else { 280 | n + m - rem 281 | } 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /src/fuse/iter.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | intrinsics::{exact_div, unchecked_sub}, 3 | marker::PhantomData, 4 | mem::size_of, 5 | ptr::{self, NonNull}, 6 | }; 7 | 8 | use super::{FuseBox, Header}; 9 | 10 | macro_rules! is_empty { 11 | ($self:ident) => { 12 | $self.headers_ptr.as_ptr() as *const _ == $self.headers_end 13 | }; 14 | } 15 | 16 | macro_rules! len { 17 | ($self:ident) => {{ 18 | let start = $self.headers_ptr.as_ptr() as usize; 19 | let size = size_of::>(); 20 | 21 | let diff = unsafe { unchecked_sub($self.headers_end as usize, start) }; 22 | unsafe { exact_div(diff, size) } 23 | }}; 24 | } 25 | 26 | macro_rules! impl_iter { 27 | ($iter:tt $(, $mut:tt)?) => { 28 | pub struct $iter<'f, Dyn> 29 | where 30 | Dyn: ?Sized, 31 | { 32 | headers_ptr: NonNull>, 33 | headers_end: *const Header, 34 | data_base_ptr: NonNull, 35 | _tag: PhantomData<&'f $($mut)? FuseBox>, 36 | } 37 | 38 | impl<'f, Dyn> $iter<'f, Dyn> 39 | where 40 | Dyn: ?Sized, 41 | { 42 | pub(crate) fn new(fused: &'f $($mut)? FuseBox) -> Self { 43 | let headers_ptr = 44 | unsafe { NonNull::new_unchecked(fused.headers.as_ptr() as *mut _) }; 45 | let headers_end = unsafe { fused.headers.as_ptr().add(fused.headers.len()) }; 46 | let data_base_ptr = fused.inner; 47 | Self { 48 | headers_ptr, 49 | headers_end, 50 | data_base_ptr, 51 | _tag: Default::default(), 52 | } 53 | } 54 | } 55 | 56 | impl<'f, Dyn> Iterator for $iter<'f, Dyn> 57 | where 58 | Dyn: ?Sized, 59 | { 60 | type Item = &'f $($mut)? Dyn; 61 | 62 | #[inline] 63 | fn next(&mut self) -> Option { 64 | if is_empty!(self) { 65 | return None; 66 | } 67 | unsafe { 68 | let next_ptr = self.headers_ptr.as_ptr(); 69 | let Header { offset, meta } = *next_ptr; 70 | 71 | let ptr = self.data_base_ptr.as_ptr().add(offset).cast(); 72 | 73 | self.headers_ptr = NonNull::new_unchecked(next_ptr.add(1)); 74 | 75 | Some(&$($mut)? *ptr::from_raw_parts_mut::(ptr, meta)) 76 | } 77 | } 78 | 79 | #[inline] 80 | fn size_hint(&self) -> (usize, Option) { 81 | let len = len!(self); 82 | (len, Some(len)) 83 | } 84 | 85 | #[inline] 86 | fn count(self) -> usize 87 | where 88 | Self: Sized, 89 | { 90 | len!(self) 91 | } 92 | 93 | #[inline] 94 | fn last(self) -> Option 95 | where 96 | Self: Sized, 97 | { 98 | if is_empty!(self) { 99 | return None; 100 | } 101 | unsafe { 102 | let Header { offset, meta } = *self.headers_end.sub(1); 103 | 104 | let ptr = self.data_base_ptr.as_ptr().add(offset).cast(); 105 | Some(& $($mut)? *ptr::from_raw_parts_mut::(ptr, meta)) 106 | } 107 | } 108 | 109 | #[inline] 110 | fn nth(&mut self, n: usize) -> Option { 111 | if n >= len!(self) { 112 | return None; 113 | } 114 | unsafe { 115 | let next_ptr = self.headers_ptr.as_ptr().add(n); 116 | let Header { offset, meta } = *next_ptr; 117 | 118 | let ptr = self.data_base_ptr.as_ptr().add(offset).cast(); 119 | 120 | self.headers_ptr = NonNull::new_unchecked(next_ptr); 121 | 122 | Some(& $($mut)? *ptr::from_raw_parts_mut::(ptr, meta)) 123 | } 124 | } 125 | } 126 | 127 | impl<'f, Dyn> ExactSizeIterator for $iter<'f, Dyn> 128 | where 129 | Dyn: ?Sized, 130 | { 131 | #[inline] 132 | fn len(&self) -> usize { 133 | len!(self) 134 | } 135 | } 136 | }; 137 | } 138 | 139 | impl_iter!(Iter); 140 | impl_iter!(IterMut, mut); 141 | -------------------------------------------------------------------------------- /src/fuse/test.rs: -------------------------------------------------------------------------------- 1 | use super::FuseBox; 2 | use std::{fmt::Debug, ops::ShlAssign}; 3 | #[test] 4 | fn test() { 5 | let mut fb = FuseBox::::default(); 6 | 7 | let v = 16u64; 8 | fb.push(v); 9 | 10 | let v = 1u8; 11 | fb.push(v); 12 | 13 | let v = 2u8; 14 | fb.push(v); 15 | 16 | let v = [1u8; 5]; 17 | fb.push(v); 18 | 19 | for v in fb.iter() { 20 | println!("{v:?}") 21 | } 22 | } 23 | 24 | #[test] 25 | fn silly() { 26 | let mut fb = FuseBox::<[u8]>::default(); 27 | 28 | let v = [0; 2]; 29 | fb.push(v); 30 | let v = [0; 4]; 31 | fb.push(v); 32 | let v = [0; 8]; 33 | fb.push(v); 34 | let v = [0; 16]; 35 | fb.push(v); 36 | 37 | for v in fb.iter() { 38 | println!("{v:?}") 39 | } 40 | } 41 | 42 | #[test] 43 | // https://github.com/JohnDowson/fusebox/issues/4 44 | fn issue4() { 45 | let mut fb = FuseBox::::default(); 46 | 47 | fb.push(42u8); 48 | fb.push(1337_u128); 49 | 50 | for v in fb.iter() { 51 | println!("{v:?}") 52 | } 53 | } 54 | 55 | #[allow(clippy::all)] 56 | #[test] 57 | // https://github.com/JohnDowson/fusebox/issues/5 58 | fn issue5() { 59 | let mut x: FuseBox = FuseBox::new(); 60 | x.push(0_u8); 61 | x.push(0_u8); 62 | 63 | x.push(0_u16); 64 | x.push(0_u8); 65 | x.push(0_u8); 66 | x.push(0_u8); 67 | x.push(0_u8); 68 | 69 | x.push(0_u16); 70 | x.push(0_u32); 71 | } 72 | 73 | #[test] 74 | fn mutate() { 75 | trait ShlDebug: ShlAssign + Debug {} 76 | impl ShlDebug for T where T: ShlAssign + Debug {} 77 | let mut fb = FuseBox::::default(); 78 | 79 | let v = 16u64; 80 | fb.push(v); 81 | 82 | let v = 1u8; 83 | fb.push(v); 84 | 85 | let v = 2u8; 86 | fb.push(v); 87 | 88 | let v = 5u32; 89 | fb.push(v); 90 | 91 | for v in fb.iter() { 92 | println!("{v:?}") 93 | } 94 | println!(); 95 | for v in fb.iter_mut() { 96 | v.shl_assign(1); 97 | } 98 | for v in fb.iter() { 99 | println!("{v:?}") 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(ptr_metadata)] 2 | #![feature(unsize)] 3 | #![feature(core_intrinsics)] 4 | #![feature(allocator_api)] 5 | #![warn(clippy::dbg_macro)] 6 | #![warn(clippy::all)] 7 | 8 | //! # fusebox 9 | //! 10 | //! Mostly safe and sound append-only collection of trait objects 11 | //! 12 | //! # Why? 13 | //! 14 | //! This avoids extra indirection of [`Vec>`] 15 | //! 16 | //! # Usage 17 | //! 18 | //! ``` 19 | //! # use std::fmt::Debug; 20 | //! # use fusebox::FuseBox; 21 | //! # #[derive(Debug)] 22 | //! # struct MyStruct {} 23 | //! let value = MyStruct {}; 24 | //! let mut fb = FuseBox::::default(); 25 | //! fb.push(value); 26 | //! ``` 27 | 28 | pub mod fuse; 29 | 30 | pub use fuse::FuseBox; 31 | --------------------------------------------------------------------------------