├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── derive ├── Cargo.toml └── lib.rs ├── src └── lib.rs └── tests ├── boxed-array.rs ├── compile_fail ├── enum.rs ├── enum.stderr ├── not-impl.rs ├── not-impl.stderr ├── union.rs └── union.stderr ├── compile_test.rs └── pass ├── basic.rs ├── generics.rs ├── nested-array.rs ├── packed-struct.rs ├── tuple-struct.rs ├── tuple.rs ├── unit-like.rs └── zero-sized.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [upsuper] 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: CI 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | profile: minimal 14 | toolchain: stable 15 | override: true 16 | - uses: actions-rs/cargo@v1 17 | with: 18 | command: check 19 | 20 | test: 21 | name: Test Suite 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v2 25 | - uses: actions-rs/toolchain@v1 26 | with: 27 | profile: minimal 28 | toolchain: stable 29 | override: true 30 | - uses: actions-rs/cargo@v1 31 | with: 32 | command: test 33 | 34 | miri: 35 | name: Miri test 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v2 39 | - uses: actions-rs/toolchain@v1 40 | with: 41 | profile: minimal 42 | toolchain: nightly 43 | override: true 44 | components: miri 45 | - name: Run tests via Miri 46 | env: 47 | MIRIFLAGS: "-Zrandomize-layout" 48 | run: cargo miri test 49 | 50 | fmt: 51 | name: Rustfmt 52 | runs-on: ubuntu-latest 53 | steps: 54 | - uses: actions/checkout@v2 55 | - uses: actions-rs/toolchain@v1 56 | with: 57 | profile: minimal 58 | toolchain: stable 59 | override: true 60 | - run: rustup component add rustfmt 61 | - uses: actions-rs/cargo@v1 62 | with: 63 | command: fmt 64 | args: --all -- --check 65 | 66 | clippy: 67 | name: Clippy 68 | runs-on: ubuntu-latest 69 | steps: 70 | - uses: actions/checkout@v2 71 | - uses: actions-rs/toolchain@v1 72 | with: 73 | profile: minimal 74 | toolchain: stable 75 | override: true 76 | - run: rustup component add clippy 77 | - uses: actions-rs/cargo@v1 78 | with: 79 | command: clippy 80 | args: -- -D warnings 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | 5 | /.idea 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "default-boxed" 3 | version = "0.2.0" 4 | description = "Helper trait to help create large struct on heap directly" 5 | authors = ["Xidorn Quan "] 6 | repository = "https://github.com/upsuper/default-boxed" 7 | keywords = ["box", "allocator", "memcpy"] 8 | categories = ["memory-management", "no-std"] 9 | edition = "2018" 10 | license = "MIT" 11 | readme = "README.md" 12 | 13 | [badges] 14 | 15 | [workspace] 16 | 17 | [dependencies] 18 | default-boxed-derive = { version = "0.2.0", path = "derive" } 19 | 20 | [target.'cfg(not(miri))'.dev-dependencies] 21 | trybuild = "1.0.28" 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Xidorn Quan 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # default-boxed 2 | 3 | [![CI](https://github.com/upsuper/default-boxed/workflows/CI/badge.svg)](https://github.com/upsuper/default-boxed/actions) 4 | [![Crates.io](https://img.shields.io/crates/v/default-boxed.svg)](https://crates.io/crates/default-boxed) 5 | 6 | 7 | 8 | Helper trait to create instances of large structs with default value on heap directly 9 | without going through stack. 10 | 11 | Similar to the unstable `box` syntax, 12 | it semantically doesn't require creating the whole struct on stack then moving to heap, 13 | and thus unlike [`copyless`][copyless] or [`boxext`][boxext], 14 | it doesn't rely on optimization to eliminate building the struct on stack, 15 | which may still face stack overflow on debug build when creating large struct. 16 | 17 | [copyless]: https://crates.io/crates/copyless 18 | [boxext]: https://crates.io/crates/boxext 19 | 20 | ## Example 21 | 22 | ```rust 23 | use default_boxed::DefaultBoxed; 24 | 25 | const BASE: usize = 1024; 26 | 27 | #[derive(DefaultBoxed)] 28 | struct Foo { 29 | a: Bar, 30 | b: [Bar; 1024 * BASE], 31 | c: [u32; 1024 * BASE], 32 | } 33 | 34 | struct Bar(u16); 35 | impl Default for Bar { 36 | fn default() -> Bar { 37 | Bar(29) 38 | } 39 | } 40 | 41 | let foo = Foo::default_boxed(); 42 | assert_eq!(foo.a.0, 29); 43 | assert_eq!(foo.b[128 * BASE].0, 29); 44 | assert_eq!(foo.c[256 * BASE], 0); 45 | 46 | let foo_arr = Foo::default_boxed_array::<16>(); 47 | assert_eq!(foo_arr[15].a.0, 29); 48 | ``` 49 | 50 | 51 | -------------------------------------------------------------------------------- /derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "default-boxed-derive" 3 | version = "0.2.0" 4 | description = "Custom derive for default-boxed" 5 | authors = ["Xidorn Quan "] 6 | repository = "https://github.com/upsuper/default-boxed" 7 | edition = "2018" 8 | license = "MIT" 9 | 10 | [lib] 11 | path = "lib.rs" 12 | proc-macro = true 13 | 14 | [dependencies] 15 | proc-macro2 = "1.0.2" 16 | quote = "1.0.2" 17 | syn = { version = "1.0.5", features = ["derive", "parsing", "printing"] } 18 | -------------------------------------------------------------------------------- /derive/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | 3 | use proc_macro2::{Span, TokenStream}; 4 | use quote::{quote, quote_spanned, ToTokens}; 5 | use syn::punctuated::Punctuated; 6 | use syn::spanned::Spanned; 7 | use syn::token::Comma; 8 | use syn::{parse_quote, Data, DeriveInput, Fields, Ident, Index, Type}; 9 | 10 | #[proc_macro_derive(DefaultBoxed)] 11 | pub fn derive_default_boxed(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 12 | derive(syn::parse_macro_input!(input)).into() 13 | } 14 | 15 | fn derive(mut input: DeriveInput) -> TokenStream { 16 | let name = &input.ident; 17 | let data = match input.data { 18 | Data::Struct(data) => data, 19 | _ => { 20 | return quote_spanned! { input.span() => 21 | compile_error!("only structs are supported"); 22 | }; 23 | } 24 | }; 25 | 26 | // Generate code to write default value into the position pointed by the ptr. 27 | let fields = match data.fields { 28 | Fields::Named(fields) => fields.named.into_iter(), 29 | Fields::Unnamed(fields) => fields.unnamed.into_iter(), 30 | Fields::Unit => Punctuated::<_, Comma>::new().into_iter(), 31 | }; 32 | let write_default: TokenStream = fields 33 | .enumerate() 34 | .map(|(i, field)| { 35 | let name = field.ident.map_or_else( 36 | || Index::from(i).to_token_stream(), 37 | |ident| ident.to_token_stream(), 38 | ); 39 | write_to_uninit("e!((*ptr).#name), &field.ty, 0) 40 | }) 41 | .collect(); 42 | 43 | if !input.generics.params.is_empty() { 44 | let mut where_clause = input.generics.where_clause.take(); 45 | let predicates = &mut where_clause.get_or_insert(parse_quote!(where)).predicates; 46 | for param in input.generics.type_params() { 47 | let ident = ¶m.ident; 48 | predicates.push(parse_quote!(#ident: ::default_boxed::DefaultBoxed)); 49 | } 50 | input.generics.where_clause = where_clause; 51 | } 52 | 53 | let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); 54 | quote! { 55 | unsafe impl#impl_generics default_boxed::DefaultBoxed for #name#ty_generics 56 | #where_clause 57 | { 58 | unsafe fn default_in_place(ptr: *mut Self) { 59 | #write_default 60 | } 61 | } 62 | } 63 | } 64 | 65 | fn write_to_uninit(var: &TokenStream, ty: &Type, array_depth: usize) -> TokenStream { 66 | match ty { 67 | Type::Array(arr) => { 68 | let array_depth = array_depth + 1; 69 | let i = format!("i{array_depth}"); 70 | let i = Ident::new(&i, Span::call_site()); 71 | let len = &arr.len; 72 | let inner = write_to_uninit("e!(#var[#i]), &arr.elem, array_depth); 73 | quote! { 74 | for #i in 0..#len { 75 | #inner 76 | } 77 | } 78 | } 79 | Type::Tuple(tuple) => tuple 80 | .elems 81 | .iter() 82 | .enumerate() 83 | .map(|(i, elem)| { 84 | let idx = Index::from(i); 85 | write_to_uninit("e!(#var.#idx), elem, array_depth) 86 | }) 87 | .collect(), 88 | ty => { 89 | let call = quote_spanned! { ty.span() => 90 | <#ty as ::default_boxed::DefaultBoxed>::default_in_place 91 | }; 92 | quote! { #call(::core::ptr::addr_of_mut!(#var)); } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(doctest), no_std)] 2 | 3 | //! Helper trait to create instances of large structs with default value on heap directly 4 | //! without going through stack. 5 | //! 6 | //! Similar to the unstable `box` syntax, 7 | //! it semantically doesn't require creating the whole struct on stack then moving to heap, 8 | //! and thus unlike [`copyless`][copyless] or [`boxext`][boxext], 9 | //! it doesn't rely on optimization to eliminate building the struct on stack, 10 | //! which may still face stack overflow on debug build when creating large struct. 11 | //! 12 | //! [copyless]: https://crates.io/crates/copyless 13 | //! [boxext]: https://crates.io/crates/boxext 14 | //! 15 | //! ## Example 16 | //! 17 | //! ``` 18 | //! use default_boxed::DefaultBoxed; 19 | //! 20 | //! # #[cfg(not(miri))] 21 | //! const BASE: usize = 1024; 22 | //! # #[cfg(miri)] 23 | //! # const BASE: usize = 1; 24 | //! 25 | //! #[derive(DefaultBoxed)] 26 | //! struct Foo { 27 | //! a: Bar, 28 | //! b: [Bar; 1024 * BASE], 29 | //! c: [u32; 1024 * BASE], 30 | //! } 31 | //! 32 | //! struct Bar(u16); 33 | //! impl Default for Bar { 34 | //! fn default() -> Bar { 35 | //! Bar(29) 36 | //! } 37 | //! } 38 | //! 39 | //! let foo = Foo::default_boxed(); 40 | //! assert_eq!(foo.a.0, 29); 41 | //! assert_eq!(foo.b[128 * BASE].0, 29); 42 | //! assert_eq!(foo.c[256 * BASE], 0); 43 | //! 44 | //! let foo_arr = Foo::default_boxed_array::<16>(); 45 | //! assert_eq!(foo_arr[15].a.0, 29); 46 | //! ``` 47 | 48 | extern crate alloc; 49 | 50 | use alloc::alloc::{alloc as alloc_raw, handle_alloc_error, Layout}; 51 | use alloc::boxed::Box; 52 | use core::ptr; 53 | 54 | pub use default_boxed_derive::DefaultBoxed; 55 | 56 | /// Helper trait to create a boxed instance of the given type with a default value for each field. 57 | /// 58 | /// This trait can be derived for structs. 59 | /// 60 | /// To derive this trait, each field needs to also implement this trait, but all types which 61 | /// implements `Default` implements this trait via the blanket `impl` already. 62 | /// 63 | /// In addition, if a field is an array, only the item type needs to implement this trait, and each 64 | /// item would be initialized separately. 65 | /// 66 | /// # Safety 67 | /// 68 | /// Implementations must ensure that `default_in_place` initializes the value on the given pointer. 69 | pub unsafe trait DefaultBoxed { 70 | /// Create a boxed instance with default value for each field. 71 | fn default_boxed() -> Box 72 | where 73 | Self: Sized, 74 | { 75 | let layout = Layout::new::(); 76 | unsafe { 77 | if layout.size() == 0 { 78 | return Box::from_raw(ptr::NonNull::::dangling().as_ptr()); 79 | } 80 | let raw = alloc_raw(layout) as *mut Self; 81 | if raw.is_null() { 82 | handle_alloc_error(layout) 83 | } else { 84 | Self::default_in_place(raw); 85 | Box::from_raw(raw) 86 | } 87 | } 88 | } 89 | 90 | /// Create a boxed array of the given size with default value of the type. 91 | /// 92 | /// ``` 93 | /// use default_boxed::DefaultBoxed; 94 | /// let arr = u32::default_boxed_array::<32>(); 95 | /// assert_eq!(arr, Box::new([0; 32])); 96 | /// ``` 97 | fn default_boxed_array() -> Box<[Self; N]> 98 | where 99 | Self: Sized, 100 | { 101 | let layout = Layout::new::<[Self; N]>(); 102 | unsafe { 103 | if layout.size() == 0 { 104 | return Box::from_raw(ptr::NonNull::<[Self; N]>::dangling().as_ptr()); 105 | } 106 | let raw = alloc_raw(layout) as *mut Self; 107 | if raw.is_null() { 108 | handle_alloc_error(layout) 109 | } else { 110 | for i in 0..N as isize { 111 | Self::default_in_place(raw.offset(i)); 112 | } 113 | Box::from_raw(raw as *mut [Self; N]) 114 | } 115 | } 116 | } 117 | 118 | /// Fill the given memory location with default value. 119 | /// 120 | /// # Safety 121 | /// 122 | /// For callers, behavior is undefined if `ptr` is not valid for writes, or it is not properly 123 | /// aligned. 124 | /// 125 | /// For impls, behavior is undefined if this method reads from `ptr`. 126 | unsafe fn default_in_place(ptr: *mut Self); 127 | } 128 | 129 | unsafe impl DefaultBoxed for T { 130 | unsafe fn default_in_place(ptr: *mut Self) { 131 | ptr::write(ptr, Default::default()); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /tests/boxed-array.rs: -------------------------------------------------------------------------------- 1 | use default_boxed::DefaultBoxed; 2 | 3 | #[test] 4 | fn test_boxed_array() { 5 | struct Foo(u32); 6 | 7 | impl Default for Foo { 8 | fn default() -> Self { 9 | Foo(0x12345678) 10 | } 11 | } 12 | 13 | let array = Foo::default_boxed_array::<1024>(); 14 | assert!(array.iter().all(|item| item.0 == 0x12345678)); 15 | } 16 | -------------------------------------------------------------------------------- /tests/compile_fail/enum.rs: -------------------------------------------------------------------------------- 1 | use default_boxed::DefaultBoxed; 2 | 3 | #[derive(DefaultBoxed)] 4 | enum Foo {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /tests/compile_fail/enum.stderr: -------------------------------------------------------------------------------- 1 | error: only structs are supported 2 | --> $DIR/enum.rs:4:1 3 | | 4 | 4 | enum Foo {} 5 | | ^^^^ 6 | -------------------------------------------------------------------------------- /tests/compile_fail/not-impl.rs: -------------------------------------------------------------------------------- 1 | use default_boxed::DefaultBoxed; 2 | 3 | struct Bar; 4 | 5 | #[derive(DefaultBoxed)] 6 | struct Foo { 7 | a: Bar, 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /tests/compile_fail/not-impl.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `Bar: Default` is not satisfied 2 | --> $DIR/not-impl.rs:7:8 3 | | 4 | 7 | a: Bar, 5 | | ^^^ the trait `Default` is not implemented for `Bar` 6 | | 7 | = note: required because of the requirements on the impl of `DefaultBoxed` for `Bar` 8 | -------------------------------------------------------------------------------- /tests/compile_fail/union.rs: -------------------------------------------------------------------------------- 1 | use default_boxed::DefaultBoxed; 2 | 3 | #[derive(DefaultBoxed)] 4 | union Foo { 5 | _a: usize, 6 | } 7 | 8 | fn main() {} 9 | -------------------------------------------------------------------------------- /tests/compile_fail/union.stderr: -------------------------------------------------------------------------------- 1 | error: only structs are supported 2 | --> $DIR/union.rs:4:1 3 | | 4 | 4 | union Foo { 5 | | ^^^^^ 6 | -------------------------------------------------------------------------------- /tests/compile_test.rs: -------------------------------------------------------------------------------- 1 | #![cfg(not(miri))] 2 | 3 | #[test] 4 | fn compile_test() { 5 | let t = trybuild::TestCases::new(); 6 | t.pass("tests/pass/*.rs"); 7 | t.compile_fail("tests/compile_fail/*.rs"); 8 | } 9 | -------------------------------------------------------------------------------- /tests/pass/basic.rs: -------------------------------------------------------------------------------- 1 | use default_boxed::DefaultBoxed; 2 | 3 | #[derive(DefaultBoxed)] 4 | struct Foo { 5 | a: u32, 6 | b: [u32; 10 * 1024 * 1024], 7 | } 8 | 9 | fn main() { 10 | let foo = Foo::default_boxed(); 11 | assert_eq!(foo.a, 0); 12 | assert_eq!(foo.b[9 * 1024 * 1024], 0); 13 | } 14 | -------------------------------------------------------------------------------- /tests/pass/generics.rs: -------------------------------------------------------------------------------- 1 | use default_boxed::DefaultBoxed; 2 | 3 | #[derive(DefaultBoxed)] 4 | struct Foo { 5 | a: T, 6 | b: [T; 100], 7 | } 8 | 9 | struct X(u32); 10 | impl Default for X { 11 | fn default() -> X { 12 | X(10) 13 | } 14 | } 15 | 16 | fn main() { 17 | let foo = Foo::::default_boxed(); 18 | assert_eq!(foo.a.0, 10); 19 | assert_eq!(foo.b[10].0, 10); 20 | } 21 | -------------------------------------------------------------------------------- /tests/pass/nested-array.rs: -------------------------------------------------------------------------------- 1 | use default_boxed::DefaultBoxed; 2 | 3 | #[derive(DefaultBoxed)] 4 | struct Foo { 5 | a: u32, 6 | b: [[u32; 10 * 1024]; 1024], 7 | } 8 | 9 | fn main() { 10 | let foo = Foo::default_boxed(); 11 | assert_eq!(foo.a, 0); 12 | assert_eq!(foo.b[512][9 * 1024], 0); 13 | } 14 | -------------------------------------------------------------------------------- /tests/pass/packed-struct.rs: -------------------------------------------------------------------------------- 1 | use default_boxed::DefaultBoxed; 2 | 3 | struct A(u8); 4 | impl Default for A { 5 | fn default() -> A { 6 | A(1) 7 | } 8 | } 9 | 10 | struct B(u32); 11 | impl Default for B { 12 | fn default() -> B { 13 | B(2) 14 | } 15 | } 16 | 17 | struct C(u16); 18 | impl Default for C { 19 | fn default() -> C { 20 | C(3) 21 | } 22 | } 23 | 24 | #[derive(DefaultBoxed)] 25 | struct Foo { 26 | a: A, 27 | b: [B; 2], 28 | c: C, 29 | } 30 | 31 | fn main() { 32 | // Assert that Rust does pack this struct. 33 | assert_eq!(std::mem::size_of::(), 12); 34 | let foo = Foo::default_boxed(); 35 | assert_eq!(foo.a.0, 1); 36 | assert_eq!(foo.b[0].0, 2); 37 | assert_eq!(foo.b[1].0, 2); 38 | assert_eq!(foo.c.0, 3); 39 | } 40 | -------------------------------------------------------------------------------- /tests/pass/tuple-struct.rs: -------------------------------------------------------------------------------- 1 | use default_boxed::DefaultBoxed; 2 | 3 | #[derive(DefaultBoxed)] 4 | struct Foo([u32; 4 * 1024 * 1024], [u16; 4 * 1024 * 1024]); 5 | 6 | fn main() { 7 | let foo = Foo::default_boxed(); 8 | assert_eq!(foo.0[1024], 0); 9 | assert_eq!(foo.1[2048], 0); 10 | } 11 | -------------------------------------------------------------------------------- /tests/pass/tuple.rs: -------------------------------------------------------------------------------- 1 | use default_boxed::DefaultBoxed; 2 | 3 | #[derive(DefaultBoxed)] 4 | struct Foo { 5 | b: ([u32; 1024 * 1024], [u32; 1024 * 1024]), 6 | } 7 | 8 | fn main() { 9 | let foo = Foo::default_boxed(); 10 | assert_eq!(foo.b.0[128 * 1024], 0); 11 | assert_eq!(foo.b.1[256 * 1024], 0); 12 | } 13 | -------------------------------------------------------------------------------- /tests/pass/unit-like.rs: -------------------------------------------------------------------------------- 1 | use default_boxed::DefaultBoxed; 2 | 3 | #[derive(DefaultBoxed)] 4 | struct Foo; 5 | 6 | fn main() { 7 | let _ = Foo::default_boxed(); 8 | } 9 | -------------------------------------------------------------------------------- /tests/pass/zero-sized.rs: -------------------------------------------------------------------------------- 1 | use default_boxed::DefaultBoxed; 2 | 3 | #[derive(DefaultBoxed)] 4 | struct Foo { 5 | _t: (), 6 | } 7 | 8 | fn main() { 9 | let _ = Foo::default_boxed(); 10 | } 11 | --------------------------------------------------------------------------------