Skip to main content

kohebi_core/
dict.rs

1//! The dict and the set.
2//!
3//! A dict remembers the order things were put into it, and that is a promise
4//! the language makes rather than an accident of the implementation, so it is
5//! not something an implementation may choose differently. The layout that
6//! gives it is CPython's compact dict and it is the one built here: a sparse
7//! table of slot numbers, and the entries themselves packed densely in a
8//! separate list in insertion order. Iterating walks the dense list, which is
9//! why iteration is in order and why it costs one pass over the live entries
10//! rather than a pass over a table that is mostly holes.
11//!
12//! It is also the cheaper of the two layouts. The sparse part holds one machine
13//! word per slot rather than a whole entry, so the table can stay two thirds
14//! empty, which is what keeps probes short, without two thirds of the memory
15//! going to nothing. That matters here more than most places, since the target
16//! is a tenth of CPython's memory and a Python program is mostly dicts.
17//!
18//! ## What is still not the real thing
19//!
20//! The slot table is a machine word per slot. CPython narrows it to one, two or
21//! four bytes for a small dict, which is most dicts, and that is worth doing but
22//! it wants the storage to be laid out by hand rather than by `Vec`.
23//!
24//! A set is a dict with nothing in the values here. CPython has a separate
25//! structure for it that saves the value word and a level of indirection.
26//! Sharing the code is the right call while the semantics are the thing being
27//! got right, and splitting it later changes nothing a caller can see.
28//!
29//! Neither has the key sharing that makes instances of a class cheap, which is
30//! what shapes are for, and shapes are a later milestone.
31
32use crate::hash::Key;
33use crate::object::Object;
34
35/// A slot nothing has ever been in, which ends a probe.
36const EMPTY: usize = usize::MAX;
37
38/// A slot something was deleted from, which does not end a probe because the
39/// key being looked for may have been put down further along the chain.
40const DUMMY: usize = usize::MAX - 1;
41
42/// The smallest table, which is what a dict gets the moment anything goes in.
43const MINIMUM: usize = 8;
44
45/// One key and value, in the order they were inserted.
46#[derive(Debug, Clone)]
47struct Entry {
48    /// Kept beside the key so a probe can reject a slot without going near
49    /// Python equality, which for a tuple means walking the whole thing.
50    hash: i64,
51    key: Key,
52    value: Object,
53}
54
55/// A Python dict.
56#[derive(Debug, Clone, Default)]
57pub struct Dict {
58    /// Slot numbers, or [`EMPTY`] or [`DUMMY`]. Always a power of two long, or
59    /// empty when nothing has been inserted yet.
60    indices: Vec<usize>,
61    /// The entries, in insertion order. A deleted one becomes `None` and stays
62    /// there until the next resize, so that the positions in `indices` keep
63    /// meaning what they meant.
64    entries: Vec<Option<Entry>>,
65    /// How many of them are still live.
66    used: usize,
67}
68
69/// The slots a hash visits, in order, until whoever is walking stops.
70///
71/// One definition of where a key belongs, used by the lookup and by the resize
72/// that lays the table out again, because the two disagreeing would file a key
73/// somewhere it would never be looked for.
74///
75/// The sequence is CPython's. The low bits of the hash pick the first slot, and
76/// each miss shifts five more of the high bits into the mix, so two keys that
77/// agree in their low bits do not then walk the same chain. Multiplying by five
78/// and adding one is what makes it reach every slot in the table, which is what
79/// stops a walk from going round forever in a table that has room.
80struct Walk {
81    mask: usize,
82    slot: usize,
83    perturb: u64,
84}
85
86impl Walk {
87    /// `size` is the length of the table, which is always a power of two.
88    #[expect(
89        clippy::cast_possible_truncation,
90        clippy::cast_sign_loss,
91        reason = "a hash is a bag of bits here rather than a number, so \
92                  dropping the sign or the top half on a 32-bit target costs \
93                  a little spread and nothing else"
94    )]
95    fn new(hash: i64, size: usize) -> Self {
96        let perturb = hash as u64;
97        Walk {
98            mask: size - 1,
99            slot: (perturb as usize) & (size - 1),
100            perturb,
101        }
102    }
103}
104
105impl Iterator for Walk {
106    type Item = usize;
107
108    /// Never ends. A table always has an empty slot in it, so every caller
109    /// stops on its own before this would have to.
110    #[expect(
111        clippy::cast_possible_truncation,
112        reason = "the same as in `new`, and for the same reason"
113    )]
114    fn next(&mut self) -> Option<usize> {
115        let slot = self.slot;
116        self.perturb >>= 5;
117        self.slot = slot
118            .wrapping_mul(5)
119            .wrapping_add(self.perturb as usize)
120            .wrapping_add(1)
121            & self.mask;
122        Some(slot)
123    }
124}
125
126/// What a probe found.
127enum Probe {
128    /// The key is here, in this slot, at this position in `entries`.
129    Occupied { slot: usize, entry: usize },
130    /// The key is not in the table, and this slot is where it would go.
131    Vacant { slot: usize },
132}
133
134impl Dict {
135    /// An empty dict, which has no table until something goes into it.
136    #[must_use]
137    pub const fn new() -> Self {
138        Dict {
139            indices: Vec::new(),
140            entries: Vec::new(),
141            used: 0,
142        }
143    }
144
145    /// How many keys are in it.
146    #[must_use]
147    pub const fn len(&self) -> usize {
148        self.used
149    }
150
151    /// Whether there are none.
152    #[must_use]
153    pub const fn is_empty(&self) -> bool {
154        self.used == 0
155    }
156
157    /// The value under this key.
158    #[must_use]
159    pub fn get(&self, key: &Key) -> Option<&Object> {
160        match self.probe(key)? {
161            Probe::Occupied { entry, .. } => Some(&self.entry(entry).value),
162            Probe::Vacant { .. } => None,
163        }
164    }
165
166    /// Whether the key is in it.
167    #[must_use]
168    pub fn contains(&self, key: &Key) -> bool {
169        self.get(key).is_some()
170    }
171
172    /// Puts a value under a key, giving back the one that was there.
173    ///
174    /// A key that is already present keeps the object it was first stored as,
175    /// which is why `d = {1: 'a'}` then `d[True] = 'b'` leaves `1` as the key
176    /// and not `True`. The two are equal, so there is nothing to update, and
177    /// replacing the key would change what iterating the dict hands back.
178    pub fn insert(&mut self, key: Key, value: Object) -> Option<Object> {
179        self.reserve();
180        // `reserve` guarantees a table, so there is always a probe to read.
181        match self
182            .probe(&key)
183            .expect("a table was just made if there was none")
184        {
185            Probe::Occupied { entry, .. } => {
186                Some(std::mem::replace(&mut self.entry_mut(entry).value, value))
187            }
188            Probe::Vacant { slot } => {
189                self.indices[slot] = self.entries.len();
190                self.entries.push(Some(Entry {
191                    hash: key.hash(),
192                    key,
193                    value,
194                }));
195                self.used += 1;
196                None
197            }
198        }
199    }
200
201    /// Takes a key out, giving back the value that was under it.
202    pub fn remove(&mut self, key: &Key) -> Option<Object> {
203        match self.probe(key)? {
204            Probe::Occupied { slot, entry } => {
205                // The slot cannot go back to empty, because a key inserted
206                // after this one may have probed past this slot to find its
207                // own, and an empty here would end that probe early and lose
208                // it. Hence the tombstone, which the next resize clears out.
209                self.indices[slot] = DUMMY;
210                let removed = self.entries[entry].take().expect("a live position");
211                self.used -= 1;
212                Some(removed.value)
213            }
214            Probe::Vacant { .. } => None,
215        }
216    }
217
218    /// Empties it, giving up the table as well.
219    pub fn clear(&mut self) {
220        *self = Dict::new();
221    }
222
223    /// The keys and values, in the order they were inserted.
224    pub fn iter(&self) -> impl Iterator<Item = (&Key, &Object)> {
225        self.entries
226            .iter()
227            .flatten()
228            .map(|entry| (&entry.key, &entry.value))
229    }
230
231    /// The keys, in the order they were inserted.
232    pub fn keys(&self) -> impl Iterator<Item = &Key> {
233        self.iter().map(|(key, _)| key)
234    }
235
236    /// The next entry at or after a raw position, and where to look next.
237    ///
238    /// An iterator over a dict cannot hold a Rust iterator, because the dict is
239    /// behind a `RefCell` and the borrow would have to outlive the step. It
240    /// holds a position instead, which is what CPython's dict iterator holds
241    /// too. The position is into the entry array rather than a count of live
242    /// entries, so a deletion earlier in the table does not silently shift what
243    /// comes next.
244    #[must_use]
245    pub fn entry_at(&self, from: usize) -> Option<(&Key, &Object, usize)> {
246        let mut at = from;
247        while let Some(slot) = self.entries.get(at) {
248            at += 1;
249            if let Some(entry) = slot {
250                return Some((&entry.key, &entry.value, at));
251            }
252        }
253        None
254    }
255
256    /// Whether two dicts have the same keys with the same values, which is
257    /// what `==` asks and which does not care what order either was built in.
258    #[must_use]
259    pub fn equals(&self, other: &Self) -> bool {
260        self.used == other.used
261            && self
262                .iter()
263                .all(|(key, value)| other.get(key).is_some_and(|found| value.same_value(found)))
264    }
265
266    /// Walks the table for a key, and says where it is or where it would go.
267    ///
268    /// `None` when there is no table at all, which is an empty dict nothing
269    /// has been put into yet. The probe is CPython's: the low bits of the hash
270    /// pick the first slot, and each miss mixes in five more of the high bits,
271    /// so keys that agree in their low bits do not then walk the same chain.
272    fn probe(&self, key: &Key) -> Option<Probe> {
273        if self.indices.is_empty() {
274            return None;
275        }
276        let hash = key.hash();
277        // A tombstone is where the key would go if it turns out not to be
278        // here, but the walk has to go on past it to find out whether it is.
279        let mut reusable = None;
280        for slot in Walk::new(hash, self.indices.len()) {
281            match self.indices[slot] {
282                EMPTY => {
283                    return Some(Probe::Vacant {
284                        slot: reusable.unwrap_or(slot),
285                    });
286                }
287                DUMMY => {
288                    if reusable.is_none() {
289                        reusable = Some(slot);
290                    }
291                }
292                entry => {
293                    let candidate = self.entry(entry);
294                    // The hash first, because it settles nearly every miss
295                    // without touching the objects.
296                    if candidate.hash == hash && candidate.key == *key {
297                        return Some(Probe::Occupied { slot, entry });
298                    }
299                }
300            }
301        }
302        unreachable!("a table is never full, so a walk always reaches an empty slot")
303    }
304
305    /// Makes sure there is room for one more before anything is probed for.
306    ///
307    /// The test is against the number of positions used rather than the number
308    /// of live keys, because a tombstone still lengthens every probe that runs
309    /// over it. Keeping a third of the table empty is what keeps those probes
310    /// short, and it is the fraction CPython settled on.
311    fn reserve(&mut self) {
312        if self.indices.is_empty() {
313            self.indices = vec![EMPTY; MINIMUM];
314            return;
315        }
316        if (self.entries.len() + 1) * 3 > self.indices.len() * 2 {
317            self.rebuild();
318        }
319    }
320
321    /// Drops the deleted entries and lays the table out again.
322    ///
323    /// The size comes from the live keys rather than from the old size, so a
324    /// dict that filled up and then emptied out shrinks rather than carrying
325    /// its high water mark around forever.
326    fn rebuild(&mut self) {
327        let wanted = (self.used + 1).saturating_mul(3).max(MINIMUM);
328        let size = wanted.next_power_of_two();
329        self.entries.retain(Option::is_some);
330
331        let mut indices = vec![EMPTY; size];
332        for (position, entry) in self.entries.iter().enumerate() {
333            let hash = entry.as_ref().expect("the holes were just dropped").hash;
334            // No tombstones and no duplicates, since these keys were already
335            // distinct, so the first empty slot is the one.
336            let slot = Walk::new(hash, size)
337                .find(|&slot| indices[slot] == EMPTY)
338                .expect("a fresh table has empty slots in it");
339            indices[slot] = position;
340        }
341        self.indices = indices;
342    }
343
344    fn entry(&self, position: usize) -> &Entry {
345        self.entries[position]
346            .as_ref()
347            .expect("a slot only ever points at a live entry")
348    }
349
350    fn entry_mut(&mut self, position: usize) -> &mut Entry {
351        self.entries[position]
352            .as_mut()
353            .expect("a slot only ever points at a live entry")
354    }
355}
356
357impl FromIterator<(Key, Object)> for Dict {
358    fn from_iter<I: IntoIterator<Item = (Key, Object)>>(pairs: I) -> Self {
359        let mut dict = Dict::new();
360        for (key, value) in pairs {
361            dict.insert(key, value);
362        }
363        dict
364    }
365}
366
367/// A Python set.
368///
369/// A dict with nothing in the values, which is what CPython's set was before it
370/// got a structure of its own. The saving from splitting them is one word an
371/// element, and it is worth taking once the semantics have stopped moving.
372#[derive(Debug, Clone, Default)]
373pub struct Set {
374    members: Dict,
375}
376
377impl Set {
378    /// An empty set.
379    #[must_use]
380    pub const fn new() -> Self {
381        Set {
382            members: Dict::new(),
383        }
384    }
385
386    /// How many members it has.
387    #[must_use]
388    pub const fn len(&self) -> usize {
389        self.members.len()
390    }
391
392    /// Whether there are none.
393    #[must_use]
394    pub const fn is_empty(&self) -> bool {
395        self.members.is_empty()
396    }
397
398    /// Whether this value is in it.
399    #[must_use]
400    pub fn contains(&self, value: &Key) -> bool {
401        self.members.contains(value)
402    }
403
404    /// Puts a value in, and says whether it was not already there.
405    pub fn insert(&mut self, value: Key) -> bool {
406        self.members.insert(value, Object::None).is_none()
407    }
408
409    /// Takes a value out, and says whether it was there.
410    pub fn remove(&mut self, value: &Key) -> bool {
411        self.members.remove(value).is_some()
412    }
413
414    /// The members.
415    ///
416    /// In insertion order, which is not something a set promises and not
417    /// something CPython gives, so nothing may lean on it.
418    pub fn iter(&self) -> impl Iterator<Item = &Key> {
419        self.members.keys()
420    }
421
422    /// The next member at or after a raw position, and where to look next.
423    ///
424    /// See [`Dict::entry_at`], which this is.
425    #[must_use]
426    pub fn member_at(&self, from: usize) -> Option<(&Key, usize)> {
427        self.members
428            .entry_at(from)
429            .map(|(key, _, next)| (key, next))
430    }
431
432    /// Whether two sets have the same members.
433    #[must_use]
434    pub fn equals(&self, other: &Self) -> bool {
435        self.len() == other.len() && self.iter().all(|value| other.contains(value))
436    }
437}
438
439impl FromIterator<Key> for Set {
440    fn from_iter<I: IntoIterator<Item = Key>>(values: I) -> Self {
441        let mut set = Set::new();
442        for value in values {
443            set.insert(value);
444        }
445        set
446    }
447}
448
449#[cfg(test)]
450mod tests {
451    use super::*;
452
453    fn key(object: Object) -> Key {
454        Key::new(object).expect("expected this to be hashable")
455    }
456
457    fn int(value: i64) -> Key {
458        key(Object::int(value))
459    }
460
461    /// The keys, in order, as integers, which is what nearly every test here
462    /// wants to say something about.
463    fn order(dict: &Dict) -> Vec<i64> {
464        dict.keys()
465            .map(|key| match key.object() {
466                Object::Int(value) => value.to_i64().expect("small enough"),
467                other => panic!("not an integer key: {}", other.repr()),
468            })
469            .collect()
470    }
471
472    #[test]
473    fn an_empty_dict_has_no_table_and_answers_anyway() {
474        let dict = Dict::new();
475        assert_eq!(dict.len(), 0);
476        assert!(dict.is_empty());
477        assert!(dict.get(&int(1)).is_none());
478        assert!(!dict.contains(&int(1)));
479        assert_eq!(order(&dict), Vec::<i64>::new());
480    }
481
482    #[test]
483    fn what_goes_in_comes_back_out() {
484        let mut dict = Dict::new();
485        assert!(dict.insert(int(1), Object::str("a")).is_none());
486        assert!(dict.insert(int(2), Object::str("b")).is_none());
487        assert_eq!(dict.len(), 2);
488        assert_eq!(dict.get(&int(1)).expect("present").repr(), "'a'");
489        assert_eq!(dict.get(&int(2)).expect("present").repr(), "'b'");
490        assert!(dict.get(&int(3)).is_none());
491    }
492
493    /// The promise the language makes about a dict, so it gets a test that
494    /// survives a resize and a deletion rather than one that only fits in the
495    /// first table.
496    #[test]
497    fn iteration_is_in_the_order_things_went_in() {
498        let mut dict: Dict = (0..50).rev().map(|n| (int(n), Object::int(n))).collect();
499        assert_eq!(order(&dict), (0..50).rev().collect::<Vec<_>>());
500
501        // Removing does not disturb what is left.
502        for n in (0..50).step_by(3) {
503            dict.remove(&int(n));
504        }
505        let expected: Vec<i64> = (0..50).rev().filter(|n| n % 3 != 0).collect();
506        assert_eq!(order(&dict), expected);
507
508        // Putting a key back puts it at the end, because it is a new key now.
509        dict.insert(int(0), Object::None);
510        let mut expected = expected;
511        expected.push(0);
512        assert_eq!(order(&dict), expected);
513    }
514
515    /// Overwriting is not reinsertion, so the key keeps the place it had.
516    #[test]
517    fn writing_over_a_key_leaves_it_where_it_was() {
518        let mut dict: Dict = (0..5).map(|n| (int(n), Object::int(n))).collect();
519        let previous = dict.insert(int(1), Object::str("new"));
520        assert_eq!(previous.expect("there was a value").repr(), "1");
521        assert_eq!(order(&dict), vec![0, 1, 2, 3, 4]);
522        assert_eq!(dict.get(&int(1)).expect("present").repr(), "'new'");
523        assert_eq!(dict.len(), 5);
524    }
525
526    /// `d = {1: 'a'}` then `d[True] = 'b'` leaves `1` as the key. The two are
527    /// equal, so there is nothing to update, and swapping the key would change
528    /// what iterating hands back.
529    #[test]
530    fn an_equal_key_does_not_replace_the_one_already_there() {
531        let mut dict = Dict::new();
532        dict.insert(int(1), Object::str("a"));
533        dict.insert(key(Object::Bool(true)), Object::str("b"));
534        assert_eq!(dict.len(), 1);
535        assert_eq!(dict.get(&int(1)).expect("present").repr(), "'b'");
536        let stored = dict.keys().next().expect("one key");
537        assert_eq!(stored.object().repr(), "1");
538    }
539
540    #[test]
541    fn the_three_numeric_types_are_one_key() {
542        let mut dict = Dict::new();
543        dict.insert(int(1), Object::str("int"));
544        dict.insert(key(Object::Float(1.0)), Object::str("float"));
545        dict.insert(key(Object::Bool(true)), Object::str("bool"));
546        assert_eq!(dict.len(), 1);
547        assert_eq!(dict.get(&int(1)).expect("present").repr(), "'bool'");
548    }
549
550    #[test]
551    fn taking_a_key_out_takes_the_value_with_it() {
552        let mut dict: Dict = (0..5).map(|n| (int(n), Object::int(n))).collect();
553        assert_eq!(dict.remove(&int(2)).expect("was there").repr(), "2");
554        assert!(dict.remove(&int(2)).is_none());
555        assert_eq!(dict.len(), 4);
556        assert!(!dict.contains(&int(2)));
557        assert_eq!(order(&dict), vec![0, 1, 3, 4]);
558    }
559
560    /// A deleted slot cannot go back to being empty, because a key inserted
561    /// after it may have probed past it. Emptying it would end that probe
562    /// early and the key would be lost while still being in the table.
563    ///
564    /// This is the bug tombstones exist to stop, and it needs enough keys and
565    /// enough deletions for two of them to have collided, which is why this
566    /// churns rather than testing one pair.
567    #[test]
568    fn a_key_is_not_lost_when_a_key_before_it_is_deleted() {
569        let mut dict = Dict::new();
570        for round in 0..40i64 {
571            for n in 0..40 {
572                dict.insert(int(round * 40 + n), Object::int(n));
573            }
574            for n in 0..40 {
575                if (round + n) % 3 == 0 {
576                    dict.remove(&int(round * 40 + n));
577                }
578            }
579            // Everything that was not deleted is still findable, every round.
580            for n in 0..40 {
581                let n = round * 40 + n;
582                let present = dict.contains(&int(n));
583                assert_eq!(present, (n / 40 + n % 40) % 3 != 0, "key {n}");
584            }
585        }
586    }
587
588    /// A dict used as a queue, which is the shape that catches a container
589    /// that never gives anything back.
590    ///
591    /// Deleting leaves a hole in both the table and the entry list, and
592    /// neither is cleared out on the spot, because a probe running over the
593    /// hole still has to reach what is past it. So the entry list does grow
594    /// under churn. What has to be true is that it grows to a bound rather
595    /// than forever, and the resize that clears the holes is what makes that
596    /// so. Without it this leaks a machine word per operation.
597    #[test]
598    fn a_dict_churned_through_does_not_grow_without_end() {
599        let mut dict = Dict::new();
600        for n in 0..10_000 {
601            dict.insert(int(n), Object::int(n));
602            dict.remove(&int(n));
603            assert!(dict.is_empty());
604        }
605        dict.insert(int(0), Object::None);
606        assert_eq!(order(&dict), vec![0]);
607        assert!(
608            dict.entries.len() <= MINIMUM,
609            "entries grew to {}",
610            dict.entries.len()
611        );
612        assert!(
613            dict.indices.len() <= MINIMUM,
614            "table grew to {}",
615            dict.indices.len()
616        );
617    }
618
619    /// The same thing with the deletions lagging behind the insertions, so
620    /// there really are live keys mixed in with the holes when the resize
621    /// comes and it has to keep the ones that are still wanted.
622    #[test]
623    fn a_sliding_window_keeps_what_is_still_in_it() {
624        let mut dict = Dict::new();
625        let width = 32;
626        for n in 0..2_000 {
627            dict.insert(int(n), Object::int(n));
628            if n >= width {
629                assert_eq!(
630                    dict.remove(&int(n - width)).expect("was there").repr(),
631                    (n - width).to_string()
632                );
633            }
634            let live = (n + 1).min(width);
635            assert_eq!(dict.len(), usize::try_from(live).expect("a small count"));
636        }
637        assert_eq!(order(&dict), (2_000 - width..2_000).collect::<Vec<_>>());
638        assert!(
639            dict.entries.len() < 200,
640            "entries grew to {}",
641            dict.entries.len()
642        );
643    }
644
645    #[test]
646    fn clearing_it_leaves_an_empty_dict_that_still_works() {
647        let mut dict: Dict = (0..20).map(|n| (int(n), Object::int(n))).collect();
648        dict.clear();
649        assert!(dict.is_empty());
650        assert!(!dict.contains(&int(1)));
651        dict.insert(int(7), Object::None);
652        assert_eq!(order(&dict), vec![7]);
653    }
654
655    /// Every key hashing to the same thing turns the table into one long
656    /// probe, which is where an off by one in the walk shows up.
657    #[test]
658    fn keys_that_all_collide_still_all_come_back() {
659        // A tuple's hash comes from its elements' hashes, so this makes fifty
660        // distinct keys out of one repeated hash by construction.
661        let colliding = |n: i64| key(Object::tuple(vec![Object::int(0), Object::int(n)]));
662        let mut dict = Dict::new();
663        for n in 0..50 {
664            dict.insert(colliding(n), Object::int(n));
665        }
666        assert_eq!(dict.len(), 50);
667        for n in 0..50 {
668            assert_eq!(
669                dict.get(&colliding(n)).expect("present").repr(),
670                n.to_string()
671            );
672        }
673        for n in (0..50).step_by(2) {
674            assert!(dict.remove(&colliding(n)).is_some());
675        }
676        for n in 0..50 {
677            assert_eq!(dict.contains(&colliding(n)), n % 2 == 1, "key {n}");
678        }
679    }
680
681    #[test]
682    fn two_dicts_are_equal_when_they_hold_the_same_thing() {
683        let a: Dict = (0..5).map(|n| (int(n), Object::int(n))).collect();
684        let b: Dict = (0..5).rev().map(|n| (int(n), Object::int(n))).collect();
685        // Order is remembered but it is not part of what makes two equal.
686        assert!(a.equals(&b));
687        assert_ne!(order(&a), order(&b));
688
689        let c: Dict = (0..4).map(|n| (int(n), Object::int(n))).collect();
690        assert!(!a.equals(&c));
691
692        // The values compare with Python's rules, so a float matches an int.
693        let d: Dict = (0..5)
694            .map(|n| (int(i64::from(n)), Object::Float(f64::from(n))))
695            .collect();
696        assert!(a.equals(&d));
697    }
698
699    #[test]
700    fn a_set_holds_each_value_once() {
701        let mut set = Set::new();
702        assert!(set.insert(int(1)));
703        assert!(!set.insert(int(1)));
704        assert!(!set.insert(key(Object::Float(1.0))));
705        assert!(set.insert(int(2)));
706        assert_eq!(set.len(), 2);
707        assert!(set.contains(&int(1)));
708        assert!(!set.contains(&int(3)));
709        assert!(set.remove(&int(1)));
710        assert!(!set.remove(&int(1)));
711        assert_eq!(set.len(), 1);
712    }
713
714    #[test]
715    fn two_sets_are_equal_when_they_hold_the_same_values() {
716        let a: Set = (0..5).map(int).collect();
717        let b: Set = (0..5).rev().map(int).collect();
718        assert!(a.equals(&b));
719        assert!(!a.equals(&(0..4).map(int).collect()));
720        assert!(!a.equals(&(1..6).map(int).collect()));
721    }
722}