1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
use crate::{Fragment, SplitVec, SplitVecGrowth};

/// A 'no-growth' strategy which does not allow the vector
/// to grow at all.
///
/// The vector must be created with sufficient capacity.
/// Exceeding this fixed capacity will lead to a panic.
///
/// The benefit of this strategy, on the other hand,
/// is its faster access by index operations;
/// which must be inlined and have a comparable performance
/// with regular slice access by index.
///
/// Further, the pinned-memory-location of already
/// pushed elements feature is maintained.
#[derive(Debug, Default, Clone, PartialEq)]
pub struct FixedCapacity;

impl<T> SplitVecGrowth<T> for FixedCapacity {
    /// Given that the split vector contains the given `fragments`,
    /// returns the capacity of the next fragment.
    ///
    /// However, the split vector with fixed capacity policy cannot grow;
    /// and hence this call panics.
    ///
    /// # Panics
    ///
    /// Panics regardless of the `fragments`. Since the split vector with
    /// fixed capacity policy cannot grow, the split vector will always
    /// contain a single fragment.
    #[allow(clippy::panic)]
    #[inline(always)]
    fn new_fragment_capacity(&self, fragments: &[Fragment<T>]) -> usize {
        debug_assert_eq!(1, fragments.len());
        panic!("a split vector with fixed capacity policy cannot grow.");
    }

    #[inline(always)]
    /// Returns the location of the element with the given `element_index` on the split vector
    /// as a tuple of (fragment-index, index-within-fragment).
    ///
    /// Returns None if the element index is out of bounds.
    ///
    /// Since a `SplitVec` with fixed capacity policy will always have a single fragment,
    /// this method will return:
    /// * `Some((0, element_index))` if `element_index` is in bounds,
    /// * `None` otherwise.
    fn get_fragment_and_inner_indices(
        &self,
        fragments: &[Fragment<T>],
        element_index: usize,
    ) -> Option<(usize, usize)> {
        debug_assert_eq!(1, fragments.len());
        if element_index < fragments[0].len() {
            Some((0, element_index))
        } else {
            None
        }
    }
}

impl<T> SplitVec<T, FixedCapacity> {
    /// Creates a split vector with the given `fixed_capacity`.
    ///
    /// This capacity is the hard limit and the vector cannot grow beyond it.
    /// Attempts to exceed this limit will lead to the code to panic.
    ///
    /// The benefit of this strategy, on the other hand,
    /// is its faster access by index operations;
    /// which must be inlined and have a comparable performance
    /// with regular slice access by index.
    ///
    /// Further, the pinned-memory-location of already
    /// pushed elements feature is maintained.
    ///
    /// # Examples
    ///
    /// ```
    /// use orx_split_vec::SplitVec;
    ///
    /// // SplitVec<usize, FixedCapacity>
    /// let mut vec = SplitVec::with_fixed_capacity(4);
    ///
    /// assert_eq!(1, vec.fragments().len());
    /// assert_eq!(Some(4), vec.fragments().first().map(|f| f.capacity()));
    /// assert_eq!(Some(0), vec.fragments().first().map(|f| f.len()));
    ///
    /// // push 4 elements to fill the vector completely
    /// for i in 0..4 {
    ///     vec.push(i);
    /// }
    /// assert_eq!(1, vec.fragments().len());
    ///
    /// // the next push exceeding the fixed_capacity will panic.
    /// // vec.push(4);
    /// ```
    pub fn with_fixed_capacity(fixed_capacity: usize) -> Self {
        Self {
            fragments: vec![Fragment::new(fixed_capacity)],
            growth: FixedCapacity,
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::{FixedCapacity, Fragment, SplitVec, SplitVecGrowth};

    #[test]
    #[should_panic]
    fn new_cap() {
        fn new_fra() -> Fragment<usize> {
            Vec::<usize>::with_capacity(10).into()
        }

        let growth = FixedCapacity;
        let _ = growth.new_fragment_capacity(&[new_fra()]);
    }

    #[test]
    #[should_panic]
    fn indices_panics_when_fragments_is_empty() {
        assert_eq!(
            None,
            <FixedCapacity as SplitVecGrowth<usize>>::get_fragment_and_inner_indices(
                &FixedCapacity,
                &[],
                0
            )
        );
    }

    #[test]
    fn indices() {
        fn new_full() -> Fragment<usize> {
            (0..10).collect::<Vec<_>>().into()
        }

        let growth = FixedCapacity;

        for i in 0..10 {
            assert_eq!(
                Some((0, i)),
                growth.get_fragment_and_inner_indices(&[new_full()], i)
            );
        }
        assert_eq!(
            None,
            growth.get_fragment_and_inner_indices(&[new_full()], 10)
        );
    }

    #[test]
    fn fixed_capacity_vec() {
        let mut vec = SplitVec::with_fixed_capacity(77);

        for i in 0..vec.capacity() {
            assert_eq!(i, vec.len());
            vec.push(i);
            assert_eq!(1, vec.fragments().len());
            assert_eq!(77, vec.capacity());
            assert_eq!(77, vec.fragments()[0].capacity());
        }
    }

    #[test]
    #[should_panic]
    fn exceeding_fixed_capacity_panics() {
        let mut vec = SplitVec::with_fixed_capacity(77);
        for i in 0..vec.capacity() {
            vec.push(i);
        }
        vec.push(42); // panics!
    }
}