dsa_rust/sequences/dyn_array.rs
1/*!
2
3# About
4
5# Design
6
7# Example
8*/
9
10use std::alloc::{self, Layout};
11use std::ops::{Index, IndexMut};
12use std::ptr::{self, NonNull};
13
14#[derive(Debug)]
15/// A dynamically sized, contiguous storage buffer with positive
16/// (forward) indexing, where elements are stored sequentially in memory.
17pub struct DynArray<T> {
18 ptr: NonNull<T>, // Pointer to the start of the contiguous region
19 len: usize, // Number of elements currently initialized
20 cap: usize, // Total number of elements the region can hold
21}
22#[allow(clippy::new_without_default)]
23#[allow(unused)] // Temporary shhhhhh
24impl<T> DynArray<T> {
25 // Utilities
26 ////////////
27
28 pub fn new() -> Self {
29 // Start with zero capacity and a "dangling" pointer to avoid
30 // an immediate syscall for an empty array
31 Self {
32 ptr: NonNull::dangling(),
33 len: 0,
34 cap: 0,
35 }
36 }
37
38 /// Pre-allocate for quicker operations
39 pub fn new_with_capacity(cap: usize) -> Self {
40 // Will panic on attempts to allocate beyond isize::MAX bytes
41 let new_layout = Layout::array::<T>(cap).unwrap();
42 // SAFETY:
43 let ptr = unsafe { alloc::alloc(new_layout) };
44 Self {
45 ptr: NonNull::new(ptr as *mut T).expect("Out of memory"),
46 len: 0,
47 cap,
48 }
49 }
50
51 fn layouts() {
52 let layout = Layout::new::<usize>();
53 }
54
55 /// Warning: Unimplemented
56 /// Returns the current number of elements in the list.
57 pub fn size(&mut self, val: T) {}
58
59 /// Warning: Unimplemented
60 /// Returns the current capacity of the list.
61 pub fn cap(&mut self, val: T) {}
62
63 /// Warning: Unimplemented
64 /// Returns a Boolean indicating whether the list is empty.
65 pub fn is_empty(&mut self, val: T) -> bool {
66 false
67 }
68
69 /// Internal capacity growth utility.
70 fn grow(&mut self) {
71 // Double the capacity (or start at 4)
72 let new_cap = if self.cap == 0 { 4 } else { self.cap * 2 };
73 let new_layout = Layout::array::<T>(new_cap).unwrap();
74
75 // SAFETY:
76 unsafe {
77 let new_ptr = if self.cap == 0 {
78 // First time allocating: ask the retailer for a fresh block
79 alloc::alloc(new_layout)
80 } else {
81 // Already have memory: ask to resize (might move to a new page)
82 let old_layout = Layout::array::<T>(self.cap).unwrap();
83 alloc::realloc(self.ptr.as_ptr() as *mut u8, old_layout, new_layout.size())
84 };
85
86 self.ptr = NonNull::new(new_ptr as *mut T).expect("Out of memory");
87 self.cap = new_cap;
88 }
89 }
90
91 // Mutators
92 ///////////
93
94 /// Appends the list with an given element in O(1) time.
95 /// If the list is at capacity, this operation automatically
96 /// grows the list in O(n) time.
97 pub fn push(&mut self, val: T) {
98 if self.len == self.cap {
99 self.grow();
100 }
101
102 // SAFETY:
103 unsafe {
104 // Calculate the offset and write the value into the uninitialized slot
105 let offset_ptr = self.ptr.as_ptr().add(self.len);
106 ptr::write(offset_ptr, val);
107 self.len += 1;
108 }
109 }
110
111 /// Warning: Unimplemented
112 /// Removes the last inserted element from the list.
113 pub fn pop(&mut self) {}
114
115 /// Warning: Unimplemented
116 /// Removes an element at the ith index in the list.
117 pub fn remove(&mut self, index: usize) {}
118
119 /// Warning: Unimplemented
120 /// Inserts an element at a given index, forcing any remaining
121 /// elements between i - list.len() to shift right. If the list
122 /// is at capacity, the list automatically grows in O(n) time.
123 pub fn insert(&mut self, val: T, index: usize) {}
124
125 /// Warning: Unimplemented
126 /// Returns
127 pub fn get(&mut self, index: usize) -> Option<T> {
128 None
129 }
130}
131impl<T> Index<usize> for DynArray<T> {
132 type Output = T;
133
134 fn index(&self, index: usize) -> &Self::Output {
135 if index >= self.len {
136 panic!(
137 "Index out of bounds: the len is {} but the index is {}",
138 self.len, index
139 );
140 }
141 // Uses pointer arithmetic to retireve the [index]th element
142 // add takes usize, for negative indexing like in Python's
143 // list type, use offset
144 // SAFETY:
145 unsafe { &*self.ptr.as_ptr().add(index) }
146 }
147}
148impl<T> IndexMut<usize> for DynArray<T> {
149 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
150 if index >= self.len {
151 panic!(
152 "Index out of bounds: the len is {} but the index is {}",
153 self.len, index
154 );
155 }
156 // SAFETY:
157 unsafe { &mut *self.ptr.as_ptr().add(index) }
158 }
159}
160impl<T> Drop for DynArray<T> {
161 fn drop(&mut self) {
162 if self.cap != 0 {
163 // SAFETY:
164 unsafe {
165 // 1. Drop the actual items in the array
166 ptr::drop_in_place(ptr::slice_from_raw_parts_mut(self.ptr.as_ptr(), self.len));
167
168 // 2. Tell the allocator to release the virtual memory region
169 let layout = Layout::array::<T>(self.cap).unwrap();
170 alloc::dealloc(self.ptr.as_ptr() as *mut u8, layout);
171 }
172 }
173 }
174}
175
176#[test]
177fn test_0() {
178 let mut a = DynArray::<&str>::new();
179 a.push("Hello");
180 a.push("world");
181
182 assert_eq!(a.len, 2);
183 assert_eq!(a.cap, 4);
184
185 // Accesses the array with index operator
186 assert_eq!(a[0], "Hello");
187 assert_eq!(a[1], "world");
188
189 a.push("Alpha");
190 a.push("Bravo");
191 assert_eq!(a.len, 4);
192 assert_eq!(a.cap, 4);
193
194 // Grows the array, doubling its capacity
195 a.push("Charlie");
196 assert_eq!(a.len, 5);
197 assert_eq!(a.cap, 8);
198
199 // Illustrates the same logic with a pre-allocated slab
200 // which avoids O(n) reallocation with the internal grow()
201 let mut a = DynArray::<String>::new_with_capacity(4);
202 a.push("Papa".to_string());
203 a.push("Echo".to_string());
204 a.push("Tango".to_string());
205 a.push("Echo".to_string());
206 a.push("Romeo".to_string());
207 assert_eq!(a.cap, 8);
208 assert_eq!(a.len, 5);
209}
210
211#[test]
212#[should_panic]
213fn test_1() {
214 let mut a = DynArray::<&str>::new();
215 a.push("Hello");
216 a.push("world");
217
218 assert_ne!(a[5], "Hello"); // Illegal index
219}
220
221#[test]
222fn test_2() {
223 let mut a = DynArray::<String>::new_with_capacity(4);
224 a.push("Papa".to_string());
225 a.push("Echo".to_string());
226 a.push("Tango".to_string());
227 a.push("Echo".to_string());
228 a.push("Romeo".to_string());
229 assert_eq!(a.cap, 8);
230 assert_eq!(a.len, 5);
231
232 //assert!(balance("([][]{([]{()})})")); // Balanced
233 //assert!(balance("([][]{[]{()}}()")); // Missing closing symbol
234 //assert!(balance("[][]{[]{()}}())")); // Missing opening symbol
235}
236
237#[test]
238#[should_panic]
239fn test_panic() {
240 // Cannot allocate beyond isize::MAX, which is aproximately
241 // 9,223,372,036,854,775,807, or ~9.22 quintillion
242 let _: DynArray<String> = DynArray::new_with_capacity(9_223_372_036_854_775_809);
243 // Panic!!
244}
245
246mod testing {
247
248 #![allow(
249 unused,
250 clippy::disallowed_names,
251 clippy::needless_lifetimes,
252 clippy::extra_unused_lifetimes
253 )]
254
255 struct MutStr {}
256
257 fn two<'a, 'long, 'short, T>() {
258 // Invariance vs covariance
259 ///////////////////////////
260 //
261 // foo is invariant, and can only take *mut T
262 let foo: *mut T;
263 // bar is covariant and can take any sub-type over T
264 let bar: std::ptr::NonNull<T>;
265
266 // Sub-typing
267 let foo: &'static str = "foo"; // Lives longest, most useful
268 let bar: &'long str = "bar"; // Lives longer, more useful
269 let baz: &'short str = "baz"; // Lives shortest, least useful
270
271 // Allowed because foo lives exactly as long
272 // as the specified lifetime
273 three(foo);
274 // Allowed because is a sub-type of the
275 // parameter and lives much longer so it
276 // can be coerced into a shorter lifetime
277 four(foo);
278 // bar is illegal because bar is a supertype
279 // and does not live as long as the parameter spec
280 //three(bar);
281
282 five(foo, foo);
283 five(bar, baz);
284 }
285
286 // Example of contravariance
287 ////////////////////////////
288 //
289 // Least useful because a caller must supply a value
290 // that lives for the entire program, instead of some
291 // shorter-lived binding.
292 fn three(foo: &'static str) {} // Stricter, so less useful
293 // More useful because a caller can supply a slice
294 // with any lifetime.
295 fn four<'a>(foo: &'a str) {} // Less strict, more useful
296
297 fn five<'a, 'b>(foo: &'a str, bar: &'b str) {
298 let a: Box<&str> = Box::from(foo);
299 println!("{foo} {bar}");
300 }
301
302 // Invariance
303 //
304 // Raw pointer type *mut T is invariant in T which
305 // means that there is no sub-typing possible through
306 // type constructor parameter, preventing any type coercion.
307 fn invariant<'a>(foo: *mut &'a str) {}
308 fn demo_invariance<'a, 'b>(bar: *mut &'a str) {
309 // Illegal because *mut T is invariant in its pointee type
310 // so it is not possible to coerce the lifetime
311 //let mut baz: *mut &'b str = bar; // Error!
312
313 // Allowed because the types are invariant,
314 // e.g. foo and bar are both *mut &'a T
315 invariant(bar);
316 }
317
318 // Covariance
319 //
320 // Shared references (&T) are covariant over their
321 // lifetime parameter, which allows coercion from
322 // longer-lived references to shorter-lived ones
323 //
324 // &'static str <: &'a str, so a &'static str can be used
325 // where &'a str is expected
326 fn covariant<'a>(foo: &'a str) {}
327 fn demo_covariance(bar: &'static str) {
328 // Allowed via covariant coerction because
329 // &'static str is a sub-type of &'a str
330 covariant(bar);
331 }
332
333 // contravariance
334 //
335 fn accepts_static(_: fn(&'static str)) {}
336 fn takes_any<'a>(_: fn(&'a str)) {}
337 fn demo_contravariance<'a>(foo: fn(&'a str)) {
338 accepts_static(foo); // OK via contravariance
339 }
340}