Skip to content

Commit f6e8da6

Browse files
committed
patina_internal_collections: Move Default trait bound to method level
Moves Default requirement from struct definitions to only the methods that need it (with_capacity, resize). Also fixes UB in both methods by properly initializing memory with MaybeUninit.
1 parent f921e1e commit f6e8da6

File tree

3 files changed

+135
-55
lines changed

3 files changed

+135
-55
lines changed

core/patina_internal_collections/src/bst.rs

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,6 @@ where
3737
Bst { storage: Storage::new(), root: Cell::new(core::ptr::null_mut()) }
3838
}
3939

40-
/// Creates a new binary tree with a given slice of memory.
41-
pub fn with_capacity(slice: &'a mut [u8]) -> Self {
42-
Self { storage: Storage::with_capacity(slice), root: Cell::default() }
43-
}
4440

4541
/// Returns the number of elements in the tree.
4642
pub fn len(&self) -> usize {
@@ -600,7 +596,7 @@ where
600596

601597
impl<D> Default for Bst<'_, D>
602598
where
603-
D: SliceKey,
599+
D: SliceKey + Default,
604600
{
605601
fn default() -> Self {
606602
Self::new()
@@ -612,17 +608,6 @@ impl<'a, D> Bst<'a, D>
612608
where
613609
D: Copy + SliceKey + 'a,
614610
{
615-
/// Replaces the memory of the tree with a new slice, copying the data from the old slice to the new slice.
616-
pub fn resize(&mut self, slice: &'a mut [u8]) {
617-
let root = { if self.root.get().is_null() { None } else { Some(self.storage.idx(self.root.get())) } };
618-
619-
self.storage.resize(slice);
620-
621-
if let Some(idx) = root {
622-
self.root.set(self.storage.get_mut(idx).expect("Pointer Exists."));
623-
}
624-
}
625-
626611
#[cfg(feature = "alloc")]
627612
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
628613
#[allow(dead_code)]
@@ -644,6 +629,38 @@ where
644629
}
645630
}
646631
}
632+
633+
/// Methods that require D to be Copy + Default.
634+
impl<'a, D> Bst<'a, D>
635+
where
636+
D: Copy + SliceKey + Default + 'a,
637+
{
638+
/// Creates a new binary tree with a given slice of memory.
639+
///
640+
/// # Trait Requirements
641+
///
642+
/// This method requires `D: Default` to initialize nodes. If your type
643+
/// doesn't implement Default, you cannot use with_capacity.
644+
pub fn with_capacity(slice: &'a mut [u8]) -> Self {
645+
Self { storage: Storage::with_capacity(slice), root: Cell::default() }
646+
}
647+
648+
/// Replaces the memory of the tree with a new slice, copying the data from the old slice to the new slice.
649+
///
650+
/// # Trait Requirements
651+
///
652+
/// This method requires `D: Default` to initialize new nodes. If your type
653+
/// doesn't implement Default, you cannot resize the storage.
654+
pub fn resize(&mut self, slice: &'a mut [u8]) {
655+
let root = { if self.root.get().is_null() { None } else { Some(self.storage.idx(self.root.get())) } };
656+
657+
self.storage.resize(slice);
658+
659+
if let Some(idx) = root {
660+
self.root.set(self.storage.get_mut(idx).expect("Pointer Exists."));
661+
}
662+
}
663+
}
647664
impl<D> core::fmt::Debug for Bst<'_, D>
648665
where
649666
D: SliceKey,
@@ -702,7 +719,7 @@ mod tests {
702719

703720
#[test]
704721
fn test_get_functions() {
705-
#[derive(Debug)]
722+
#[derive(Debug, Default, Copy, Clone)]
706723
struct MyType(usize, usize);
707724
impl crate::SliceKey for MyType {
708725
type Key = usize;

core/patina_internal_collections/src/node.rs

Lines changed: 88 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//!
77
//! SPDX-License-Identifier: Apache-2.0
88
//!
9-
use core::{cell::Cell, mem, ptr::NonNull, slice};
9+
use core::{cell::Cell, mem, mem::MaybeUninit, ptr::NonNull, slice};
1010

1111
use crate::{Error, Result, SliceKey};
1212

@@ -49,28 +49,6 @@ where
4949
}
5050
}
5151

52-
/// Create a new storage container with a slice of memory.
53-
pub fn with_capacity(slice: &'a mut [u8]) -> Storage<'a, D> {
54-
let storage = Storage {
55-
// SAFETY: This is reinterpreting a byte slice as a Node<D> slice.
56-
// 1. The alignment is checked implicitly by the slice bounds.
57-
// 2. The correct number of Node<D> elements that fit in the byte slice is calculated.
58-
// 3. The lifetime ensures the byte slice remains valid for the storage's lifetime
59-
data: unsafe {
60-
slice::from_raw_parts_mut::<'a, Node<D>>(
61-
slice as *mut [u8] as *mut Node<D>,
62-
slice.len() / mem::size_of::<Node<D>>(),
63-
)
64-
},
65-
length: 0,
66-
available: Cell::default(),
67-
};
68-
69-
Self::build_linked_list(storage.data);
70-
storage.available.set(storage.data[0].as_mut_ptr());
71-
storage
72-
}
73-
7452
fn build_linked_list(buffer: &[Node<D>]) {
7553
let mut node = &buffer[0];
7654
for next in buffer.iter().skip(1) {
@@ -176,8 +154,55 @@ where
176154

177155
impl<'a, D> Storage<'a, D>
178156
where
179-
D: SliceKey + Copy,
157+
D: SliceKey + Copy + Default,
180158
{
159+
/// Create a new storage container with a slice of memory.
160+
///
161+
/// # Trait Requirements
162+
///
163+
/// This method requires `D: Default` to initialize nodes. If your type
164+
/// doesn't implement Default, you cannot use with_capacity.
165+
pub fn with_capacity(slice: &'a mut [u8]) -> Storage<'a, D> {
166+
// SAFETY: This is reinterpreting a byte slice as a MaybeUninit<Node<D>> slice.
167+
// Using MaybeUninit explicitly represents uninitialized memory and avoids undefined
168+
// behavior from creating references to uninitialized Node<D>.
169+
let uninit_buffer = unsafe {
170+
slice::from_raw_parts_mut::<'a, MaybeUninit<Node<D>>>(
171+
slice as *mut [u8] as *mut MaybeUninit<Node<D>>,
172+
slice.len() / mem::size_of::<Node<D>>(),
173+
)
174+
};
175+
176+
// Initialize all nodes with Default values
177+
for elem in uninit_buffer.iter_mut() {
178+
// SAFETY: We're initializing uninitialized memory using MaybeUninit::write,
179+
// which is the safe way to initialize MaybeUninit without reading the old value.
180+
elem.write(Node::new(D::default()));
181+
}
182+
183+
// SAFETY: All elements have been initialized in the loop above.
184+
// We can now safely convert from MaybeUninit<Node<D>> to Node<D>.
185+
let buffer = unsafe {
186+
slice::from_raw_parts_mut(
187+
uninit_buffer.as_mut_ptr() as *mut Node<D>,
188+
uninit_buffer.len()
189+
)
190+
};
191+
192+
let storage = Storage {
193+
data: buffer,
194+
length: 0,
195+
available: Cell::default(),
196+
};
197+
198+
if !storage.data.is_empty() {
199+
Self::build_linked_list(storage.data);
200+
storage.available.set(storage.data[0].as_mut_ptr());
201+
}
202+
203+
storage
204+
}
205+
181206
/// Resizes the storage container to a new slice of memory.
182207
///
183208
/// # Panics
@@ -187,18 +212,50 @@ where
187212
/// # Time Complexity
188213
///
189214
/// O(n)
215+
///
216+
/// # Trait Requirements
217+
///
218+
/// This method requires `D: Default` to initialize new nodes.
190219
pub fn resize(&mut self, slice: &'a mut [u8]) {
191-
// SAFETY: This is reinterpreting a byte slice as a Node<D> slice.
192-
// 1. The alignment is handled by slice casting rules
193-
// 2. The correct number of Node<D> elements that fit in the byte slice is calculated
194-
// 3. The lifetime 'a ensures the byte slice remains valid for the storage's lifetime
195-
let buffer = unsafe {
196-
slice::from_raw_parts_mut::<'a, Node<D>>(
197-
slice as *mut [u8] as *mut Node<D>,
220+
// SAFETY: This is reinterpreting a byte slice as a MaybeUninit<Node<D>> slice.
221+
// Using MaybeUninit explicitly represents uninitialized memory and avoids undefined
222+
// behavior from creating references to uninitialized Node<D>.
223+
let uninit_buffer = unsafe {
224+
slice::from_raw_parts_mut::<'a, MaybeUninit<Node<D>>>(
225+
slice as *mut [u8] as *mut MaybeUninit<Node<D>>,
198226
slice.len() / mem::size_of::<Node<D>>(),
199227
)
200228
};
201229

230+
assert!(uninit_buffer.len() >= self.len());
231+
232+
// When current capacity is 0, we need to initialize all nodes
233+
if self.capacity() == 0 {
234+
// Initialize all nodes since we have no existing data
235+
for elem in uninit_buffer.iter_mut() {
236+
elem.write(Node::new(D::default()));
237+
}
238+
} else {
239+
// Only initialize NEW nodes (beyond self.len())
240+
// The first self.len() nodes will have their data copied from old buffer
241+
for elem in uninit_buffer[..self.len()].iter_mut() {
242+
// Initialize with a temporary value that will be immediately overwritten
243+
elem.write(Node::new(D::default()));
244+
}
245+
// Initialize only the new nodes (beyond current length)
246+
for elem in uninit_buffer[self.len()..].iter_mut() {
247+
elem.write(Node::new(D::default()));
248+
}
249+
}
250+
251+
// SAFETY: All elements have been initialized. Convert to Node<D> slice.
252+
let buffer = unsafe {
253+
slice::from_raw_parts_mut(
254+
uninit_buffer.as_mut_ptr() as *mut Node<D>,
255+
uninit_buffer.len()
256+
)
257+
};
258+
202259
assert!(buffer.len() >= self.len());
203260

204261
// When current capacity is 0, we just need to copy the data and build the available list

core/patina_internal_collections/src/rbt.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,6 @@ where
3939
Rbt { storage: Storage::new(), root: Cell::new(core::ptr::null_mut()) }
4040
}
4141

42-
/// Creates a new binary tree with a given slice of memory.
43-
pub fn with_capacity(slice: &'a mut [u8]) -> Self {
44-
Rbt { storage: Storage::with_capacity(slice), root: Cell::default() }
45-
}
4642

4743
/// Returns the number of elements in the tree.
4844
pub fn len(&self) -> usize {
@@ -841,8 +837,18 @@ where
841837

842838
impl<'a, D> Rbt<'a, D>
843839
where
844-
D: SliceKey + Copy + 'a,
840+
D: SliceKey + Copy + Default + 'a,
845841
{
842+
/// Creates a new binary tree with a given slice of memory.
843+
///
844+
/// # Trait Requirements
845+
///
846+
/// This method requires `D: Default` to initialize nodes. If your type
847+
/// doesn't implement Default, you cannot use with_capacity.
848+
pub fn with_capacity(slice: &'a mut [u8]) -> Self {
849+
Rbt { storage: Storage::with_capacity(slice), root: Cell::default() }
850+
}
851+
846852
/// Replaces the memory of the tree with a new slice, copying the data from the old slice to the new slice.
847853
pub fn resize(&mut self, slice: &'a mut [u8]) {
848854
let root = (!self.root.get().is_null()).then(|| self.storage.idx(self.root.get()));
@@ -887,7 +893,7 @@ where
887893

888894
impl<D> core::fmt::Debug for Rbt<'_, D>
889895
where
890-
D: SliceKey,
896+
D: SliceKey + Default,
891897
{
892898
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
893899
f.debug_struct("Rbt")
@@ -1646,7 +1652,7 @@ mod tests {
16461652

16471653
#[test]
16481654
fn test_get_functions() {
1649-
#[derive(Debug)]
1655+
#[derive(Debug, Default, Copy, Clone)]
16501656
struct MyType(usize, usize);
16511657
impl crate::SliceKey for MyType {
16521658
type Key = usize;

0 commit comments

Comments
 (0)