x_map/maps.rs
1//! The primary maps module for `x-map`. This module contains all code relating to the various
2//! maps that are packaged as part of `x-map`.
3//!
4//! Many map types in this module utilize raw pointers. This unsafe code is managed internally by each
5//! map contained within, and this unsafe code should be abstracted away from library users.
6//!
7//! To support multiple environments, many maps will also contain functions for low-level operations,
8//! so that in environments such as `#![no_std]` environments, these maps are still usable.
9//!
10//! As a rule of thumb, all functions in which it is expected to retrieve a value will retrieve a
11//! reference to that value, not a copy of it.
12
13use crate::error::{CIndexMapError, MapErrorKind};
14use crate::util::mem_cmp;
15use core::alloc::Layout;
16use core::fmt::Debug;
17use core::ptr;
18
19#[derive(Debug)]
20pub struct CIndexMap<K, V> {
21 /// The initial layout used to initialize this map's keys store.
22 key_layout: Layout,
23
24 /// The initial layout used to initialize this map's values store.
25 val_layout: Layout,
26
27 /// A pointer to a space in memory in which keys will be stored at.
28 keys: *mut K,
29
30 /// A pointer to a space in memory in which values will be stored at.
31 values: *mut V,
32
33 /// The total allocated memory for this map.
34 ///
35 /// Everything in the range `self.pos + 1..self.cap` is not uninitialized, but may be old
36 /// or unwanted.
37 cap: isize,
38
39 /// Position of the cursor where the **last valid value** was inserted. `keys[pos]` and
40 /// `values[pos]` will always be initialized with valid data.
41 ///
42 /// All elements 0 to `self.pos` are guaranteed to be initialized.
43 pos: isize,
44}
45
46impl<K, V> CIndexMap<K, V> {
47 /// Constructs a new `CIndexMap`.
48 ///
49 /// # Panics
50 /// When `size_of::<K>` or `size_of::<T>` is 0.
51 pub fn new() -> CIndexMap<K, V> {
52 if size_of::<K>() == 0 || size_of::<V>() == 0 {
53 panic!("Cannot initialize CIndexMap with ZSTs!");
54 }
55
56 let key_layout = unsafe {
57 Layout::from_size_align(size_of::<K>() * 1, align_of::<K>()).unwrap()
58 };
59 let val_layout = unsafe {
60 Layout::from_size_align(size_of::<V>() * 1, align_of::<V>()).unwrap()
61 };
62
63 // SAFETY:
64 // ZSTs are not supported, this point in the initializing cannot be reached
65 // if a ZST is provided.
66 let key_ptr = unsafe {
67 std::alloc::alloc(key_layout)
68 } as *mut K;
69
70 // SAFETY:
71 // ZSTs are not supported, this point in the initializing cannot be reached
72 // if a ZST is provided.
73 let val_ptr = unsafe {
74 std::alloc::alloc(val_layout)
75 } as *mut V;
76
77 CIndexMap {
78 key_layout,
79 val_layout,
80 keys: key_ptr,
81 values: val_ptr,
82 cap: 1,
83 pos: -1,
84 }
85 }
86
87 /// Inserts a new key/value pair into the map.
88 ///
89 /// This function returns `Ok(())` if the key/value pair was inserted successfully.
90 pub fn insert(&mut self, key: K, value: V) -> crate::result::Result<()> {
91 if (self.pos + 1) >= self.cap {
92 let new_cap = self.cap + 2;
93
94 let new_key_ptr = unsafe {
95 std::alloc::realloc(
96 self.keys as *mut u8,
97 self.key_layout,
98 size_of::<K>() * (new_cap as usize),
99 ) as *mut K
100 };
101
102 let new_val_ptr = unsafe {
103 std::alloc::realloc(
104 self.values as *mut u8,
105 self.val_layout,
106 size_of::<V>() * (new_cap as usize),
107 ) as *mut V
108 };
109
110 if (new_val_ptr == ptr::null_mut()) || (new_key_ptr == ptr::null_mut()) {
111 return Err(CIndexMapError::new(
112 MapErrorKind::AllocationError,
113 "Error when attempting to allocate map memory."
114 ));
115 } else {
116 self.keys = new_key_ptr;
117 self.values = new_val_ptr;
118 self.cap = new_cap;
119 }
120 }
121
122 self.pos += 1;
123
124 // SAFETY:
125 // The data was properly aligned during initialization.
126 // The memory being pointed to was verified to be between 0 (the pointer starting position)
127 // and `self.cap`, which is the current amount of allocated space.
128 unsafe {
129 self.keys.offset(self.pos).write(key);
130 self.values.offset(self.pos).write(value);
131 }
132
133 Ok(())
134 }
135
136 /// Removes an element at the specified index.
137 pub fn remove(&mut self, index: usize) -> crate::result::Result<()> {
138 if index > (self.pos as usize) {
139 Err(
140 CIndexMapError::new(
141 MapErrorKind::AccessError,
142 "Attempted to access a map index that surpasses the bounds of the current map."
143 )
144 )
145 } else {
146 // SAFETY:
147 // We've determined that the index requested fits within the range of valid data.
148 unsafe {
149 // Shift all elements between `index` and `self.pos` back by one by copying
150 // the pointer and overwriting it at the position specified by `index`.
151 ptr::copy(
152 self.keys.offset((index as isize) + 1),
153 self.keys.offset(index as isize),
154 (self.pos as usize - index),
155 );
156
157 ptr::copy(
158 self.values.offset((index as isize) + 1),
159 self.values.offset(index as isize),
160 self.pos as usize - index,
161 );
162 }
163
164 // Decrement self.pos to ensure this is always set as the position of the
165 // last valid inserted value.
166 self.pos -= 1;
167 Ok(())
168 }
169 }
170
171 /// Returns the key to the entry at the specified index.
172 ///
173 /// Use `CIndexMap::get` to get the value of the entry based off of the key.
174 pub fn index(&self, index: usize) -> crate::result::Result<&K> {
175 if index > (self.pos as usize) {
176 Err(
177 CIndexMapError::new(
178 MapErrorKind::AccessError,
179 "Attempted to access a map index that surpasses the bounds of the current map."
180 )
181 )
182 } else {
183 // SAFETY:
184 // All elements `0..self.pos` should be already be initialized.
185 unsafe {
186 Ok(
187 &*self.keys.offset(index as isize)
188 )
189 }
190 }
191 }
192
193 /// Gets the value associated with the specified key.
194 ///
195 /// This function is preferred for when the underlying Key/Value types for this map cannot,
196 /// for some reason, implement `PartialEq`.
197 ///
198 /// This performs a raw memory comparison between the supplied value and the keys in the
199 /// current map. This comparison doesn't work on a lot of dynamically sized types, like `String`
200 /// and `Vec`.
201 /// # Correct usage
202 /// ```
203 /// use x_map::maps::CIndexMap;
204 /// let mut map = CIndexMap::new();
205 /// map.insert("foo", "bar").unwrap();
206 ///
207 /// // Prints `map.get("foo").unwrap() = "bar"`
208 /// dbg!(map.get("foo").unwrap());
209 ///
210 /// // Prints `map.get_no_peq("foo").unwrap() = "bar"`
211 /// dbg!(map.get_no_peq("foo").unwrap());
212 /// ```
213 ///
214 ///
215 /// # Incorrect usage
216 /// ```
217 /// use x_map::maps::CIndexMap;
218 /// let string_one = String::from("foo");
219 /// let string_two = String::from("bar");
220 ///
221 /// let mut map = CIndexMap::new();
222 /// map.insert(string_one.to_string(), string_two.to_string()).unwrap();
223 ///
224 ///
225 /// // Prints `map.get(string_one.to_string()) = Some("bar")`
226 /// dbg!(map.get(string_one.to_string()));
227 ///
228 /// // Prints `map.get_no_peq(string_one.to_string()) = None`
229 /// dbg!(map.get_no_peq(string_one.to_string()));
230 /// ```
231 pub fn get_no_peq(&self, key: K) -> Option<&V> {
232 let key_ptr = ptr::from_ref(&key);
233 let mut i = 0;
234
235 // SAFETY:
236 // We only iterate the memory space between 0 and self.pos, which is always initialized.
237 //
238 // The type system guarantees that the supplied `key`, the position of the pointer, and the
239 // `size_of::<K>` are all already valid. This means `mem_cmp` can safely be called,
240 // as the data in `self.keys[0..self.pos]` is already initialized, and is valid for both
241 // the size of `key`, and the size of type `K`.
242 unsafe {
243 while i <= self.pos {
244 let cmp = mem_cmp(key_ptr as *const u8, self.keys.offset(i) as *const u8, size_of::<K>());
245 match cmp {
246 None => { return Some(&*self.values.offset(i)); }
247 Some(_) => {}
248 }
249
250 i += 1;
251 }
252 }
253
254 None
255 }
256}
257
258impl<K: PartialEq, V> CIndexMap<K, V> {
259 /// Gets the value associated with the specified key.
260 ///
261 /// Multiple implementations exist for `get`:
262 /// - When `K` does not implement `PartialEq`, `get` will perform bytewise comparison between
263 /// the input and the map keys. This is a fallback implementation, and will not always produce
264 /// expected results. This implementation will have unexpected results on dynamically sized
265 /// types, such as `String` and `Vec`.
266 ///
267 /// - When `K` implements `PartialEq`, `get` will call `PartialEq::partial_eq()` for comparison
268 /// between the input and the map keys. This is the preferred implementation, and will produce
269 /// the expected results.
270 pub fn get(&self, key: K) -> Option<&V> {
271 // SAFETY:
272 // We know that all elements from (self.keys + 0) to (self.keys + self.pos) are initialized.
273 // Thus, reading from memory for each allocation of size_of::<K> is correct.
274 unsafe {
275 for i in 0..(self.pos + 1) {
276 if *self.keys.add(i as usize) == key {
277 return Some(&*self.values.add(i as usize));
278 }
279 }
280 };
281
282 None
283 }
284
285 /// Checks if the map contains the provided key.
286 ///
287 /// - If it does, this function returns `true`,
288 /// - If it does not, this function returns `false`.
289 pub fn contains_key(&self, key: K) -> bool {
290 // SAFETY:
291 // We only iterate over the data between 0 and self.pos, which is always initialized.
292 unsafe {
293 for i in 0..(self.pos + 1) {
294 if *self.keys.add(i as usize) == key {
295 return true;
296 }
297 }
298 };
299
300 false
301 }
302}
303
304impl<K, V: PartialEq> CIndexMap<K, V> {
305 /// Checks if the map contains the provided value.
306 ///
307 /// - If it does, this function returns `true`,
308 /// - If it does not, this function returns `false`.
309 pub fn contains_value(&self, value: V) -> bool {
310 // SAFETY:
311 // We only iterate over the data between 0 and self.pos, which is always initialized.
312 unsafe {
313 for i in 0..(self.pos + 1) {
314 if *self.values.add(i as usize) == value {
315 return true;
316 }
317 }
318 };
319
320 false
321 }
322}