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