From 58088dce3bd740219e3d85831f9f775ec42645eb Mon Sep 17 00:00:00 2001 From: Hugh Kaznowski Date: Tue, 15 Oct 2024 14:29:08 +0100 Subject: [PATCH 1/2] Add try_push_uninit and test --- .gitignore | 2 ++ src/arrayvec.rs | 16 ++++++++++++++++ tests/tests.rs | 32 +++++++++++++++++++++++++++++++- 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 00d01286..ac44273f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ # Generated by Cargo /Cargo.lock /target +.idea + diff --git a/src/arrayvec.rs b/src/arrayvec.rs index e87b3ef7..a7b1436a 100644 --- a/src/arrayvec.rs +++ b/src/arrayvec.rs @@ -206,6 +206,22 @@ impl ArrayVec { ArrayVecImpl::try_push(self, element) } + /// Push a new uninitialised value to the end of the vector and return + /// a mutable reference to it. + /// + /// This is useful if the backed value is large and won't fit on stack. + /// + /// Capacity error carries the data - since we don't have data then we need a different error type. + pub unsafe fn try_push_uninit(&mut self) -> Result<*mut T, ()> { + let len = self.len(); + if len >= Self::CAPACITY { + return Err(()) + } + let new_ptr = self.as_mut_ptr().add(len); + self.set_len(len + 1); + Ok(new_ptr) + } + /// Push `element` to the end of the vector without checking the capacity. /// /// It is up to the caller to ensure the capacity of the vector is diff --git a/tests/tests.rs b/tests/tests.rs index 16508b76..a3d9e1f1 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -7,7 +7,7 @@ use std::mem; use arrayvec::CapacityError; use std::collections::HashMap; - +use std::sync::RwLock; #[test] fn test_simple() { @@ -777,3 +777,33 @@ fn test_arraystring_zero_filled_has_some_sanity_checks() { assert_eq!(string.as_str(), "\0\0\0\0"); assert_eq!(string.len(), 4); } + +/// 3 MB struct +struct ReallyBigStruct { + pub(crate) field_one: [u8; 1_000_000], + pub(crate) field_two: [u8; 1_000_000], + pub(crate) field_three: [u8; 1_000_000], +} + +/// Const initialised struct outside of stack +/// We need to initialise this outside of the stack, since otherwise there is a memory copy from +/// the stack into the heap. +/// With a static initialisation, we do not have a stack copy. +static large_struct: RwLock> = RwLock::new(ArrayVec::new_const()); + +#[test] +fn test_push_uninit() { + let mut lock = large_struct.write().unwrap(); + let ptr = unsafe { lock.try_push_uninit().unwrap() }; + let field_one = unsafe {&mut (*ptr).field_one}; + *field_one = [1; 1_000_000]; + let field_two = unsafe {&mut (*ptr).field_two}; + *field_two = [2; 1_000_000]; + let field_three = unsafe {&mut (*ptr).field_three}; + *field_three = [3; 1_000_000]; + + assert_eq!(lock.len(), 1); + assert_eq!(lock[0].field_one[3], 1); + assert_eq!(lock[0].field_two[999], 2); + assert_eq!(lock[0].field_three[999_999], 3); +} From 33ac6afb786faac110233faaabd9f50296195174 Mon Sep 17 00:00:00 2001 From: Hugh Kaznowski Date: Tue, 15 Oct 2024 15:39:03 +0100 Subject: [PATCH 2/2] Add second push to test --- tests/tests.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/tests.rs b/tests/tests.rs index a3d9e1f1..5321134b 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -806,4 +806,21 @@ fn test_push_uninit() { assert_eq!(lock[0].field_one[3], 1); assert_eq!(lock[0].field_two[999], 2); assert_eq!(lock[0].field_three[999_999], 3); + + // Push a second value + let ptr = unsafe { lock.try_push_uninit().unwrap() }; + let field_one = unsafe {&mut (*ptr).field_one}; + *field_one = [4; 1_000_000]; + let field_two = unsafe {&mut (*ptr).field_two}; + *field_two = [5; 1_000_000]; + let field_three = unsafe {&mut (*ptr).field_three}; + *field_three = [6; 1_000_000]; + + assert_eq!(lock.len(), 2); + assert_eq!(lock[0].field_one[3], 1); + assert_eq!(lock[0].field_two[999], 2); + assert_eq!(lock[0].field_three[999_999], 3); + assert_eq!(lock[1].field_one[3], 4); + assert_eq!(lock[1].field_two[999], 5); + assert_eq!(lock[1].field_three[999_999], 6); }