Skip to main content

shape_value/v2/
typed_map.rs

1//! Typed hash map for v2 runtime.
2//!
3//! `TypedMap<K, V>` is a `#[repr(C)]` open-addressing hash table with linear
4//! probing. The compiler monomorphizes: `HashMap<string, number>` and
5//! `HashMap<string, i32>` are different `TypedMap` instantiations.
6//!
7//! Concrete type aliases are provided for common instantiations used by FFI/JIT:
8//! - `TypedMapStringF64` — `HashMap<string, number>`
9//! - `TypedMapStringI64` — `HashMap<string, i64>`
10//! - `TypedMapStringPtr` — `HashMap<string, *const u8>`
11
12use super::heap_header::{HeapHeader, HEAP_KIND_V2_TYPED_MAP};
13use std::alloc::{Layout, alloc_zeroed, dealloc};
14
15/// Sentinel hash values for bucket state.
16const HASH_EMPTY: u64 = 0;
17const HASH_TOMBSTONE: u64 = 1;
18
19/// Minimum hash value for an occupied bucket (after masking).
20const HASH_MIN_OCCUPIED: u64 = 2;
21
22/// Load factor threshold (75%) — grow when `len * 4 >= bucket_count * 3`.
23const LOAD_FACTOR_NUM: u32 = 3;
24const LOAD_FACTOR_DEN: u32 = 4;
25
26/// Default initial bucket count.
27const INITIAL_CAPACITY: u32 = 8;
28
29/// A single bucket in the hash table.
30#[repr(C)]
31pub struct Bucket<K, V> {
32    /// 0 = empty, 1 = tombstone, >= 2 = occupied (stores masked hash).
33    pub hash: u64,
34    pub key: K,
35    pub value: V,
36}
37
38/// Typed open-addressing hash map with linear probing.
39#[repr(C)]
40pub struct TypedMap<K, V> {
41    pub header: HeapHeader,
42    /// Pointer to bucket array.
43    pub buckets: *mut Bucket<K, V>,
44    /// Number of buckets (always a power of 2).
45    pub bucket_count: u32,
46    /// Number of live entries.
47    pub len: u32,
48    /// Number of tombstones.
49    pub tombstone_count: u32,
50    pub _pad: u32,
51}
52
53// Concrete type aliases for common instantiations.
54pub type TypedMapStringF64 = TypedMap<*const u8, f64>;
55pub type TypedMapStringI64 = TypedMap<*const u8, i64>;
56pub type TypedMapStringPtr = TypedMap<*const u8, *const u8>;
57
58// i64-keyed map aliases.
59pub type TypedMapI64F64 = TypedMap<i64, f64>;
60pub type TypedMapI64I64 = TypedMap<i64, i64>;
61pub type TypedMapI64Ptr = TypedMap<i64, *const u8>;
62
63/// FNV-1a hash for byte slices. Simple, fast, good distribution for short keys.
64#[inline]
65fn fnv1a_hash(bytes: &[u8]) -> u64 {
66    let mut h: u64 = 0xcbf29ce484222325;
67    for &b in bytes {
68        h ^= b as u64;
69        h = h.wrapping_mul(0x100000001b3);
70    }
71    h
72}
73
74/// Ensure a hash value is >= HASH_MIN_OCCUPIED (so it can't be confused with
75/// empty/tombstone sentinels).
76#[inline]
77fn fix_hash(h: u64) -> u64 {
78    if h < HASH_MIN_OCCUPIED {
79        h.wrapping_add(HASH_MIN_OCCUPIED)
80    } else {
81        h
82    }
83}
84
85impl<K: Copy, V: Copy> TypedMap<K, V> {
86    /// Allocate a new empty TypedMap.
87    pub fn new() -> *mut Self {
88        let layout = Layout::new::<Self>();
89        let ptr = unsafe { alloc_zeroed(layout) as *mut Self };
90        assert!(!ptr.is_null(), "allocation failed for TypedMap");
91
92        unsafe {
93            (*ptr).header = HeapHeader::new(HEAP_KIND_V2_TYPED_MAP);
94            (*ptr).buckets = std::ptr::null_mut();
95            (*ptr).bucket_count = 0;
96            (*ptr).len = 0;
97            (*ptr).tombstone_count = 0;
98            (*ptr)._pad = 0;
99        }
100        ptr
101    }
102
103    /// Number of entries.
104    ///
105    /// # Safety
106    /// `this` must point to a valid `TypedMap`.
107    #[inline]
108    pub unsafe fn len(this: *const Self) -> u32 {
109        unsafe { (*this).len }
110    }
111
112    /// Whether the map is empty.
113    ///
114    /// # Safety
115    /// `this` must point to a valid `TypedMap`.
116    #[inline]
117    pub unsafe fn is_empty(this: *const Self) -> bool {
118        unsafe { (*this).len == 0 }
119    }
120
121    /// Deallocate the map and its bucket array.
122    ///
123    /// # Safety
124    /// `ptr` must point to a `TypedMap` allocated by this module.
125    pub unsafe fn drop_map(ptr: *mut Self) {
126        unsafe {
127            let map = &*ptr;
128            if map.bucket_count > 0 && !map.buckets.is_null() {
129                let bucket_layout = Layout::array::<Bucket<K, V>>(map.bucket_count as usize)
130                    .expect("invalid bucket layout");
131                dealloc(map.buckets as *mut u8, bucket_layout);
132            }
133            let layout = Layout::new::<Self>();
134            dealloc(ptr as *mut u8, layout);
135        }
136    }
137
138    /// Allocate a zeroed bucket array. All buckets have hash = 0 (empty).
139    fn alloc_buckets(count: u32) -> *mut Bucket<K, V> {
140        let layout =
141            Layout::array::<Bucket<K, V>>(count as usize).expect("invalid bucket layout");
142        let ptr = unsafe { alloc_zeroed(layout) as *mut Bucket<K, V> };
143        assert!(!ptr.is_null(), "allocation failed for TypedMap buckets");
144        ptr
145    }
146
147    /// Ensure the table has room for at least one more entry, growing if needed.
148    ///
149    /// # Safety
150    /// `this` must point to a valid `TypedMap`.
151    unsafe fn ensure_capacity(this: *mut Self) {
152        unsafe {
153            let map = &*this;
154            if map.bucket_count == 0 {
155                (*this).buckets = Self::alloc_buckets(INITIAL_CAPACITY);
156                (*this).bucket_count = INITIAL_CAPACITY;
157                return;
158            }
159            // Grow when (len + tombstones) * 4 >= bucket_count * 3
160            let used = map.len + map.tombstone_count;
161            if used * LOAD_FACTOR_DEN >= map.bucket_count * LOAD_FACTOR_NUM {
162                Self::grow(this);
163            }
164        }
165    }
166
167    /// Double the bucket array and rehash all live entries.
168    ///
169    /// # Safety
170    /// `this` must point to a valid `TypedMap` with bucket_count > 0.
171    unsafe fn grow(this: *mut Self) {
172        unsafe {
173            let map = &*this;
174            let old_buckets = map.buckets;
175            let old_count = map.bucket_count;
176
177            let new_count = old_count * 2;
178            let new_buckets = Self::alloc_buckets(new_count);
179            let mask = new_count - 1; // power-of-2 mask
180
181            // Rehash live entries
182            for i in 0..old_count {
183                let bucket = &*old_buckets.add(i as usize);
184                if bucket.hash >= HASH_MIN_OCCUPIED {
185                    let mut idx = (bucket.hash as u32) & mask;
186                    loop {
187                        let dst = &*new_buckets.add(idx as usize);
188                        if dst.hash == HASH_EMPTY {
189                            std::ptr::write(
190                                new_buckets.add(idx as usize),
191                                Bucket {
192                                    hash: bucket.hash,
193                                    key: bucket.key,
194                                    value: bucket.value,
195                                },
196                            );
197                            break;
198                        }
199                        idx = (idx + 1) & mask;
200                    }
201                }
202            }
203
204            // Free old buckets
205            let old_layout = Layout::array::<Bucket<K, V>>(old_count as usize)
206                .expect("invalid bucket layout");
207            dealloc(old_buckets as *mut u8, old_layout);
208
209            (*this).buckets = new_buckets;
210            (*this).bucket_count = new_count;
211            (*this).tombstone_count = 0;
212        }
213    }
214}
215
216// --- String-keyed map operations ---
217//
218// These operate on TypedMap<*const u8, V> where keys are `*const StringObj`.
219// We compare by string content, not pointer identity.
220
221use super::string_obj::StringObj;
222
223impl<V: Copy> TypedMap<*const u8, V> {
224    /// Insert a key-value pair. If the key already exists, updates the value
225    /// and returns the old value.
226    ///
227    /// The map retains the key pointer (caller must ensure it stays alive via
228    /// refcounting). If the key already exists, the existing key pointer is kept.
229    ///
230    /// # Safety
231    /// `this` must point to a valid `TypedMap`. `key` must point to a valid `StringObj`.
232    pub unsafe fn insert(this: *mut Self, key: *const StringObj, value: V) -> Option<V> {
233        unsafe {
234            Self::ensure_capacity(this);
235
236            let key_str = StringObj::as_str(key);
237            let hash = fix_hash(fnv1a_hash(key_str.as_bytes()));
238            let map = &*this;
239            let mask = map.bucket_count - 1;
240            let mut idx = (hash as u32) & mask;
241            let mut first_tombstone: Option<u32> = None;
242
243            loop {
244                let bucket = &*map.buckets.add(idx as usize);
245                match bucket.hash {
246                    HASH_EMPTY => {
247                        // Key not found — insert at first tombstone if we saw one, else here.
248                        let insert_idx = first_tombstone.unwrap_or(idx);
249                        if first_tombstone.is_some() {
250                            (*this).tombstone_count -= 1;
251                        }
252                        std::ptr::write(
253                            (*this).buckets.add(insert_idx as usize),
254                            Bucket {
255                                hash,
256                                key: key as *const u8,
257                                value,
258                            },
259                        );
260                        (*this).len += 1;
261                        return None;
262                    }
263                    HASH_TOMBSTONE => {
264                        if first_tombstone.is_none() {
265                            first_tombstone = Some(idx);
266                        }
267                    }
268                    h if h == hash => {
269                        // Hash matches — compare key content.
270                        let existing_key = bucket.key as *const StringObj;
271                        let existing_str = StringObj::as_str(existing_key);
272                        if existing_str == key_str {
273                            // Key exists — update value, return old.
274                            let old = bucket.value;
275                            (*(*this).buckets.add(idx as usize)).value = value;
276                            return Some(old);
277                        }
278                    }
279                    _ => {}
280                }
281                idx = (idx + 1) & mask;
282            }
283        }
284    }
285
286    /// Look up a value by string key.
287    ///
288    /// # Safety
289    /// `this` must point to a valid `TypedMap`. `key` must point to a valid `StringObj`.
290    pub unsafe fn get(this: *const Self, key: *const StringObj) -> Option<V> {
291        unsafe {
292            let map = &*this;
293            if map.len == 0 || map.bucket_count == 0 {
294                return None;
295            }
296
297            let key_str = StringObj::as_str(key);
298            let hash = fix_hash(fnv1a_hash(key_str.as_bytes()));
299            let mask = map.bucket_count - 1;
300            let mut idx = (hash as u32) & mask;
301
302            loop {
303                let bucket = &*map.buckets.add(idx as usize);
304                match bucket.hash {
305                    HASH_EMPTY => return None,
306                    HASH_TOMBSTONE => {}
307                    h if h == hash => {
308                        let existing_key = bucket.key as *const StringObj;
309                        let existing_str = StringObj::as_str(existing_key);
310                        if existing_str == key_str {
311                            return Some(bucket.value);
312                        }
313                    }
314                    _ => {}
315                }
316                idx = (idx + 1) & mask;
317            }
318        }
319    }
320
321    /// Check if the map contains a key.
322    ///
323    /// # Safety
324    /// `this` must point to a valid `TypedMap`. `key` must point to a valid `StringObj`.
325    pub unsafe fn contains_key(this: *const Self, key: *const StringObj) -> bool {
326        unsafe { Self::get(this, key).is_some() }
327    }
328
329    /// Remove a key from the map, returning the value if it was present.
330    ///
331    /// # Safety
332    /// `this` must point to a valid `TypedMap`. `key` must point to a valid `StringObj`.
333    pub unsafe fn remove(this: *mut Self, key: *const StringObj) -> Option<V> {
334        unsafe {
335            let map = &*this;
336            if map.len == 0 || map.bucket_count == 0 {
337                return None;
338            }
339
340            let key_str = StringObj::as_str(key);
341            let hash = fix_hash(fnv1a_hash(key_str.as_bytes()));
342            let mask = map.bucket_count - 1;
343            let mut idx = (hash as u32) & mask;
344
345            loop {
346                let bucket = &*map.buckets.add(idx as usize);
347                match bucket.hash {
348                    HASH_EMPTY => return None,
349                    HASH_TOMBSTONE => {}
350                    h if h == hash => {
351                        let existing_key = bucket.key as *const StringObj;
352                        let existing_str = StringObj::as_str(existing_key);
353                        if existing_str == key_str {
354                            let old_value = bucket.value;
355                            // Replace with tombstone
356                            (*(*this).buckets.add(idx as usize)).hash = HASH_TOMBSTONE;
357                            (*this).len -= 1;
358                            (*this).tombstone_count += 1;
359                            return Some(old_value);
360                        }
361                    }
362                    _ => {}
363                }
364                idx = (idx + 1) & mask;
365            }
366        }
367    }
368}
369
370// --- i64-keyed map operations ---
371//
372// These operate on TypedMap<i64, V>. Keys are compared by raw integer
373// equality. The hash mixes the i64 with FNV-1a over its bytes for a decent
374// distribution across small integers.
375
376impl<V: Copy> TypedMap<i64, V> {
377    /// Insert a key-value pair. If the key already exists, updates the value
378    /// and returns the old value.
379    ///
380    /// # Safety
381    /// `this` must point to a valid `TypedMap<i64, V>`.
382    pub unsafe fn insert_i64(this: *mut Self, key: i64, value: V) -> Option<V> {
383        unsafe {
384            Self::ensure_capacity(this);
385
386            let hash = fix_hash(fnv1a_hash(&key.to_le_bytes()));
387            let map = &*this;
388            let mask = map.bucket_count - 1;
389            let mut idx = (hash as u32) & mask;
390            let mut first_tombstone: Option<u32> = None;
391
392            loop {
393                let bucket = &*map.buckets.add(idx as usize);
394                match bucket.hash {
395                    HASH_EMPTY => {
396                        let insert_idx = first_tombstone.unwrap_or(idx);
397                        if first_tombstone.is_some() {
398                            (*this).tombstone_count -= 1;
399                        }
400                        std::ptr::write(
401                            (*this).buckets.add(insert_idx as usize),
402                            Bucket { hash, key, value },
403                        );
404                        (*this).len += 1;
405                        return None;
406                    }
407                    HASH_TOMBSTONE => {
408                        if first_tombstone.is_none() {
409                            first_tombstone = Some(idx);
410                        }
411                    }
412                    h if h == hash => {
413                        if bucket.key == key {
414                            let old = bucket.value;
415                            (*(*this).buckets.add(idx as usize)).value = value;
416                            return Some(old);
417                        }
418                    }
419                    _ => {}
420                }
421                idx = (idx + 1) & mask;
422            }
423        }
424    }
425
426    /// Look up a value by i64 key.
427    ///
428    /// # Safety
429    /// `this` must point to a valid `TypedMap<i64, V>`.
430    pub unsafe fn get_i64(this: *const Self, key: i64) -> Option<V> {
431        unsafe {
432            let map = &*this;
433            if map.len == 0 || map.bucket_count == 0 {
434                return None;
435            }
436
437            let hash = fix_hash(fnv1a_hash(&key.to_le_bytes()));
438            let mask = map.bucket_count - 1;
439            let mut idx = (hash as u32) & mask;
440
441            loop {
442                let bucket = &*map.buckets.add(idx as usize);
443                match bucket.hash {
444                    HASH_EMPTY => return None,
445                    HASH_TOMBSTONE => {}
446                    h if h == hash => {
447                        if bucket.key == key {
448                            return Some(bucket.value);
449                        }
450                    }
451                    _ => {}
452                }
453                idx = (idx + 1) & mask;
454            }
455        }
456    }
457
458    /// Check if the map contains an i64 key.
459    ///
460    /// # Safety
461    /// `this` must point to a valid `TypedMap<i64, V>`.
462    pub unsafe fn contains_key_i64(this: *const Self, key: i64) -> bool {
463        unsafe { Self::get_i64(this, key).is_some() }
464    }
465
466    /// Remove an i64 key from the map, returning the value if it was present.
467    ///
468    /// # Safety
469    /// `this` must point to a valid `TypedMap<i64, V>`.
470    pub unsafe fn remove_i64(this: *mut Self, key: i64) -> Option<V> {
471        unsafe {
472            let map = &*this;
473            if map.len == 0 || map.bucket_count == 0 {
474                return None;
475            }
476
477            let hash = fix_hash(fnv1a_hash(&key.to_le_bytes()));
478            let mask = map.bucket_count - 1;
479            let mut idx = (hash as u32) & mask;
480
481            loop {
482                let bucket = &*map.buckets.add(idx as usize);
483                match bucket.hash {
484                    HASH_EMPTY => return None,
485                    HASH_TOMBSTONE => {}
486                    h if h == hash => {
487                        if bucket.key == key {
488                            let old_value = bucket.value;
489                            (*(*this).buckets.add(idx as usize)).hash = HASH_TOMBSTONE;
490                            (*this).len -= 1;
491                            (*this).tombstone_count += 1;
492                            return Some(old_value);
493                        }
494                    }
495                    _ => {}
496                }
497                idx = (idx + 1) & mask;
498            }
499        }
500    }
501}
502
503#[cfg(test)]
504mod tests {
505    use super::*;
506
507    /// Helper: create a StringObj from a &str for testing.
508    fn make_key(s: &str) -> *mut StringObj {
509        StringObj::new(s)
510    }
511
512    #[test]
513    fn test_new_empty_map() {
514        let map = TypedMapStringF64::new();
515        unsafe {
516            assert_eq!(TypedMap::len(map), 0);
517            assert!(TypedMap::is_empty(map));
518            assert_eq!((*map).header.kind(), HEAP_KIND_V2_TYPED_MAP);
519            assert_eq!((*map).header.get_refcount(), 1);
520            TypedMap::drop_map(map);
521        }
522    }
523
524    #[test]
525    fn test_insert_and_get() {
526        let map = TypedMapStringF64::new();
527        let k1 = make_key("x");
528        let k2 = make_key("y");
529        let k3 = make_key("z");
530
531        unsafe {
532            assert_eq!(TypedMap::insert(map, k1, 1.0), None);
533            assert_eq!(TypedMap::insert(map, k2, 2.0), None);
534            assert_eq!(TypedMap::insert(map, k3, 3.0), None);
535            assert_eq!(TypedMap::len(map), 3);
536
537            // Look up using fresh key pointers (same content, different allocation)
538            let lookup_x = make_key("x");
539            let lookup_y = make_key("y");
540            let lookup_z = make_key("z");
541
542            assert_eq!(TypedMap::get(map, lookup_x), Some(1.0));
543            assert_eq!(TypedMap::get(map, lookup_y), Some(2.0));
544            assert_eq!(TypedMap::get(map, lookup_z), Some(3.0));
545
546            let missing = make_key("missing");
547            assert_eq!(TypedMap::get(map, missing), None);
548
549            StringObj::drop(lookup_x);
550            StringObj::drop(lookup_y);
551            StringObj::drop(lookup_z);
552            StringObj::drop(missing);
553            StringObj::drop(k1);
554            StringObj::drop(k2);
555            StringObj::drop(k3);
556            TypedMap::drop_map(map);
557        }
558    }
559
560    #[test]
561    fn test_insert_update_returns_old_value() {
562        let map = TypedMapStringF64::new();
563        let k1 = make_key("key");
564        let k2 = make_key("key"); // same content
565
566        unsafe {
567            assert_eq!(TypedMap::insert(map, k1, 1.0), None);
568            assert_eq!(TypedMap::insert(map, k2, 2.0), Some(1.0)); // returns old
569            assert_eq!(TypedMap::len(map), 1); // still 1 entry
570
571            let lookup = make_key("key");
572            assert_eq!(TypedMap::get(map, lookup), Some(2.0)); // updated value
573
574            StringObj::drop(lookup);
575            StringObj::drop(k1);
576            StringObj::drop(k2);
577            TypedMap::drop_map(map);
578        }
579    }
580
581    #[test]
582    fn test_contains_key() {
583        let map = TypedMapStringF64::new();
584        let k = make_key("present");
585
586        unsafe {
587            TypedMap::insert(map, k, 42.0);
588
589            let lookup_yes = make_key("present");
590            let lookup_no = make_key("absent");
591            assert!(TypedMap::contains_key(map, lookup_yes));
592            assert!(!TypedMap::contains_key(map, lookup_no));
593
594            StringObj::drop(lookup_yes);
595            StringObj::drop(lookup_no);
596            StringObj::drop(k);
597            TypedMap::drop_map(map);
598        }
599    }
600
601    #[test]
602    fn test_remove() {
603        let map = TypedMapStringI64::new();
604        let k1 = make_key("a");
605        let k2 = make_key("b");
606
607        unsafe {
608            TypedMap::insert(map, k1, 10i64);
609            TypedMap::insert(map, k2, 20i64);
610            assert_eq!(TypedMap::len(map), 2);
611
612            let rm_key = make_key("a");
613            assert_eq!(TypedMap::remove(map, rm_key), Some(10i64));
614            assert_eq!(TypedMap::len(map), 1);
615
616            // Can't find removed key
617            let lookup = make_key("a");
618            assert_eq!(TypedMap::get(map, lookup), None);
619
620            // Other key still present
621            let lookup_b = make_key("b");
622            assert_eq!(TypedMap::get(map, lookup_b), Some(20i64));
623
624            // Remove non-existent key returns None
625            let rm_missing = make_key("missing");
626            assert_eq!(TypedMap::remove(map, rm_missing), None);
627
628            StringObj::drop(rm_key);
629            StringObj::drop(lookup);
630            StringObj::drop(lookup_b);
631            StringObj::drop(rm_missing);
632            StringObj::drop(k1);
633            StringObj::drop(k2);
634            TypedMap::drop_map(map);
635        }
636    }
637
638    #[test]
639    fn test_grow_and_rehash() {
640        let map = TypedMapStringF64::new();
641        let mut keys = Vec::new();
642
643        unsafe {
644            // Insert enough entries to trigger multiple grows.
645            for i in 0..50 {
646                let key = make_key(&format!("key_{i}"));
647                TypedMap::insert(map, key, i as f64);
648                keys.push(key);
649            }
650
651            assert_eq!(TypedMap::len(map), 50);
652
653            // Verify all entries are still accessible after rehashing.
654            for i in 0..50 {
655                let lookup = make_key(&format!("key_{i}"));
656                assert_eq!(
657                    TypedMap::get(map, lookup),
658                    Some(i as f64),
659                    "missing key_{i} after grow"
660                );
661                StringObj::drop(lookup);
662            }
663
664            for k in keys {
665                StringObj::drop(k);
666            }
667            TypedMap::drop_map(map);
668        }
669    }
670
671    #[test]
672    fn test_collision_handling() {
673        // Insert many keys — some will collide due to hash masking.
674        let map = TypedMapStringF64::new();
675        let mut keys = Vec::new();
676
677        unsafe {
678            for i in 0..20 {
679                let key = make_key(&format!("{i}"));
680                TypedMap::insert(map, key, i as f64);
681                keys.push(key);
682            }
683
684            assert_eq!(TypedMap::len(map), 20);
685
686            for i in 0..20 {
687                let lookup = make_key(&format!("{i}"));
688                assert_eq!(TypedMap::get(map, lookup), Some(i as f64));
689                StringObj::drop(lookup);
690            }
691
692            for k in keys {
693                StringObj::drop(k);
694            }
695            TypedMap::drop_map(map);
696        }
697    }
698
699    #[test]
700    fn test_remove_then_insert_reuses_tombstone() {
701        let map = TypedMapStringF64::new();
702
703        unsafe {
704            let k1 = make_key("alpha");
705            TypedMap::insert(map, k1, 1.0);
706
707            let rm = make_key("alpha");
708            TypedMap::remove(map, rm);
709            assert_eq!(TypedMap::len(map), 0);
710            assert_eq!((*map).tombstone_count, 1);
711
712            // Insert same key again — should reuse tombstone slot.
713            let k2 = make_key("alpha");
714            TypedMap::insert(map, k2, 2.0);
715            assert_eq!(TypedMap::len(map), 1);
716            assert_eq!((*map).tombstone_count, 0);
717
718            let lookup = make_key("alpha");
719            assert_eq!(TypedMap::get(map, lookup), Some(2.0));
720
721            StringObj::drop(lookup);
722            StringObj::drop(k1);
723            StringObj::drop(k2);
724            StringObj::drop(rm);
725            TypedMap::drop_map(map);
726        }
727    }
728
729    #[test]
730    fn test_get_on_empty_map() {
731        let map = TypedMapStringF64::new();
732        unsafe {
733            let k = make_key("anything");
734            assert_eq!(TypedMap::get(map, k), None);
735            StringObj::drop(k);
736            TypedMap::drop_map(map);
737        }
738    }
739
740    #[test]
741    fn test_string_ptr_map() {
742        let map = TypedMapStringPtr::new();
743        unsafe {
744            let k = make_key("ptr_key");
745            let val_str = make_key("value");
746            TypedMap::insert(map, k, val_str as *const u8);
747
748            let lookup = make_key("ptr_key");
749            let result = TypedMap::get(map, lookup);
750            assert!(result.is_some());
751            let retrieved = result.unwrap() as *const StringObj;
752            assert_eq!(StringObj::as_str(retrieved), "value");
753
754            StringObj::drop(lookup);
755            StringObj::drop(k);
756            StringObj::drop(val_str);
757            TypedMap::drop_map(map);
758        }
759    }
760}