Skip to main content

multiversx_sc/storage/mappers/
vec_mapper.rs

1use super::{
2    StorageClearable, StorageMapper, StorageMapperFromAddress,
3    source::{CurrentStorage, StorageAddress},
4};
5use crate::{
6    abi::{TypeAbi, TypeAbiFrom, TypeDescriptionContainer, TypeName},
7    api::{ErrorApiImpl, StorageMapperApi},
8    codec::{
9        EncodeErrorHandler, TopDecode, TopEncode, TopEncodeMulti, TopEncodeMultiOutput,
10        multi_encode_iter_or_handle_err,
11    },
12    storage::{StorageKey, storage_clear, storage_set},
13    types::{ManagedAddress, ManagedType, MultiValueEncoded},
14};
15use core::marker::PhantomData;
16
17const LEN_SUFFIX: &str = ".len";
18const ITEM_SUFFIX: &str = ".item";
19
20const INDEX_OUT_OF_RANGE_ERR_MSG: &str = "index out of range";
21
22/// A storage mapper for managing an ordered, indexable list of items with automatic length tracking.
23///
24/// # Storage Layout
25///
26/// The `VecMapper` stores data in storage using the following pattern:
27///
28/// - `base_key + ".len"` → number of items in the list
29/// - `base_key + ".item" + index` → value at index (1-based indexing)
30///
31/// **Important**: Indexes start from 1, not 0. This avoids confusion between uninitialized variables
32/// and the zero value. Valid indexes are always in the range `1..=len()`.
33///
34/// # Main Operations
35///
36/// - **Append**: `push(item)` - Adds an item to the end. O(1) with 2 storage writes (item + length).
37/// - **Random Access**: `get(index)` - Retrieves value at index (1-based). O(1) with one storage read.
38/// - **Update**: `set(index, item)` - Replaces value at index. O(1) with one storage write.
39/// - **Remove**: `swap_remove(index)` - Removes by swapping with last element. O(1) with storage writes.
40/// - **Iteration**: `iter()` - Iterates over all values in order from index 1 to length.
41/// - **Batch Operations**: `extend_from_slice(items)` - Adds multiple items efficiently (single length update).
42///
43/// # Trade-offs
44///
45/// - **Pros**: Maintains insertion order; efficient random access; automatic length tracking.
46/// - **Cons**: Uses 1-based indexing (not 0-based); removal (except last item) changes element positions;
47///   clearing large lists can consume significant gas.
48///
49/// # Example
50///
51/// ```rust
52/// # use multiversx_sc::storage::mappers::{StorageMapper, VecMapper};
53/// # fn example<SA: multiversx_sc::api::StorageMapperApi>() {
54/// # let mut mapper = VecMapper::<SA, u64>::new(
55/// #     multiversx_sc::storage::StorageKey::new(&b"my_vec"[..])
56/// # );
57/// mapper.push(&100);
58/// mapper.push(&200);
59/// mapper.push(&300);
60///
61/// assert_eq!(mapper.len(), 3);
62/// assert_eq!(mapper.get(1), 100);  // Note: 1-based indexing
63/// assert_eq!(mapper.get(2), 200);
64///
65/// mapper.set(2, &250);
66/// assert_eq!(mapper.get(2), 250);
67///
68/// mapper.swap_remove(1);  // Removes 100, moves 300 to index 1
69/// assert_eq!(mapper.len(), 2);
70/// assert_eq!(mapper.get(1), 300);
71/// # }
72/// ```
73pub struct VecMapper<SA, T, A = CurrentStorage>
74where
75    SA: StorageMapperApi,
76    T: TopEncode + TopDecode + 'static,
77{
78    _phantom_api: PhantomData<SA>,
79    address: A,
80    base_key: StorageKey<SA>,
81    len_key: StorageKey<SA>,
82    _phantom_item: PhantomData<T>,
83}
84
85impl<SA, T> StorageMapper<SA> for VecMapper<SA, T, CurrentStorage>
86where
87    SA: StorageMapperApi,
88    T: TopEncode + TopDecode,
89{
90    fn new(base_key: StorageKey<SA>) -> Self {
91        let mut len_key = base_key.clone();
92        len_key.append_bytes(LEN_SUFFIX.as_bytes());
93
94        VecMapper {
95            _phantom_api: PhantomData,
96            address: CurrentStorage,
97            base_key,
98            len_key,
99            _phantom_item: PhantomData,
100        }
101    }
102}
103
104impl<SA, T> StorageMapperFromAddress<SA> for VecMapper<SA, T, ManagedAddress<SA>>
105where
106    SA: StorageMapperApi,
107    T: TopEncode + TopDecode,
108{
109    fn new_from_address(address: ManagedAddress<SA>, base_key: StorageKey<SA>) -> Self {
110        let mut len_key = base_key.clone();
111        len_key.append_bytes(LEN_SUFFIX.as_bytes());
112
113        VecMapper {
114            _phantom_api: PhantomData,
115            address,
116            base_key,
117            len_key,
118            _phantom_item: PhantomData,
119        }
120    }
121}
122
123impl<SA, T> StorageClearable for VecMapper<SA, T, CurrentStorage>
124where
125    SA: StorageMapperApi,
126    T: TopEncode + TopDecode,
127{
128    fn clear(&mut self) {
129        self.clear();
130    }
131}
132
133impl<SA, T, A> VecMapper<SA, T, A>
134where
135    SA: StorageMapperApi,
136    A: StorageAddress<SA>,
137    T: TopEncode + TopDecode,
138{
139    fn item_key(&self, index: usize) -> StorageKey<SA> {
140        let mut item_key = self.base_key.clone();
141        item_key.append_bytes(ITEM_SUFFIX.as_bytes());
142        item_key.append_item(&index);
143        item_key
144    }
145
146    /// Number of items managed by the mapper.
147    pub fn len(&self) -> usize {
148        self.address.address_storage_get(self.len_key.as_ref())
149    }
150
151    /// True if no items present in the mapper.
152    pub fn is_empty(&self) -> bool {
153        self.len() == 0
154    }
155
156    /// Get item at index from storage.
157    /// Index must be valid (1 <= index <= count).
158    pub fn get(&self, index: usize) -> T {
159        if index == 0 || index > self.len() {
160            SA::error_api_impl().signal_error(INDEX_OUT_OF_RANGE_ERR_MSG.as_bytes());
161        }
162        self.get_unchecked(index)
163    }
164
165    /// Get item at index from storage.
166    /// There are no restrictions on the index,
167    /// calling for an invalid index will simply return the zero-value.
168    pub fn get_unchecked(&self, index: usize) -> T {
169        self.address
170            .address_storage_get(self.item_key(index).as_ref())
171    }
172
173    /// Get item at index from storage.
174    /// If index is valid (1 <= index <= count), returns value at index,
175    /// else calls lambda given as argument.
176    /// The lambda only gets called lazily if the index is not valid.
177    pub fn get_or_else<F: FnOnce() -> T>(self, index: usize, or_else: F) -> T {
178        if index == 0 || index > self.len() {
179            or_else()
180        } else {
181            self.get_unchecked(index)
182        }
183    }
184
185    /// Checks whether or not there is anything ins storage at index.
186    /// Index must be valid (1 <= index <= count).
187    pub fn item_is_empty(&self, index: usize) -> bool {
188        if index == 0 || index > self.len() {
189            SA::error_api_impl().signal_error(INDEX_OUT_OF_RANGE_ERR_MSG.as_bytes());
190        }
191        self.item_is_empty_unchecked(index)
192    }
193
194    /// Checks whether or not there is anything in storage at index.
195    /// There are no restrictions on the index,
196    /// calling for an invalid index will simply return `true`.
197    pub fn item_is_empty_unchecked(&self, index: usize) -> bool {
198        self.address
199            .address_storage_get_len(self.item_key(index).as_ref())
200            == 0
201    }
202
203    /// Loads all items from storage and places them in a Vec.
204    /// Can easily consume a lot of gas.
205    #[cfg(feature = "alloc")]
206    pub fn load_as_vec(&self) -> alloc::vec::Vec<T> {
207        self.iter().collect()
208    }
209
210    /// Provides a forward iterator.
211    pub fn iter(&self) -> Iter<'_, SA, T, A> {
212        Iter::new(self)
213    }
214}
215
216impl<SA, T> VecMapper<SA, T, CurrentStorage>
217where
218    SA: StorageMapperApi,
219    T: TopEncode + TopDecode,
220{
221    fn save_count(&self, new_len: usize) {
222        storage_set(self.len_key.as_ref(), &new_len);
223    }
224
225    /// Add one item at the end of the list.
226    /// Returns the index of the newly inserted item, which is also equal to the new number of elements.
227    pub fn push(&mut self, item: &T) -> usize {
228        let mut len = self.len();
229        len += 1;
230        storage_set(self.item_key(len).as_ref(), item);
231        self.save_count(len);
232        len
233    }
234
235    /// Adds multiple items at the end of the list.
236    /// Cheaper than multiple `push`-es because the count only gets updated once at the end.
237    /// Returns the index of the last inserted item, which is also equal to the new number of elements.
238    pub fn extend_from_slice(&mut self, items: &[T]) -> usize {
239        let mut len = self.len();
240        for item in items {
241            len += 1;
242            storage_set(self.item_key(len).as_ref(), item);
243        }
244        self.save_count(len);
245        len
246    }
247
248    /// Set item at index in storage.
249    /// Index must be valid (1 <= index <= count).
250    pub fn set(&mut self, index: usize, item: &T) {
251        if index == 0 || index > self.len() {
252            SA::error_api_impl().signal_error(&b"index out of range"[..]);
253        }
254        self.set_unchecked(index, item);
255    }
256
257    /// Syntactic sugar, to more compactly express a get, update and set in one line.
258    /// Takes whatever lies in storage, apples the given closure and saves the final value back to storage.
259    /// Propagates the return value of the given function.
260    pub fn update<R, F: FnOnce(&mut T) -> R>(&mut self, index: usize, f: F) -> R {
261        let mut value = self.get(index);
262        let result = f(&mut value);
263        self.set(index, &value);
264        result
265    }
266
267    /// Keeping `set_unchecked` private on purpose, so developers don't write out of index limits by accident.
268    fn set_unchecked(&self, index: usize, item: &T) {
269        storage_set(self.item_key(index).as_ref(), item);
270    }
271
272    /// Clears item at index from storage.
273    /// Index must be valid (1 <= index <= count).
274    pub fn clear_entry(&self, index: usize) {
275        if index == 0 || index > self.len() {
276            SA::error_api_impl().signal_error(&b"index out of range"[..]);
277        }
278        self.clear_entry_unchecked(index)
279    }
280
281    /// Clears item at index from storage.
282    /// There are no restrictions on the index,
283    /// calling for an invalid index will simply do nothing.
284    pub fn clear_entry_unchecked(&self, index: usize) {
285        storage_clear(self.item_key(index).as_ref());
286    }
287
288    /// Clears item at index from storage by swap remove
289    /// last item takes the index of the item to remove
290    /// and we remove the last index.
291    pub fn swap_remove(&mut self, index: usize) {
292        let _ = self.swap_remove_and_get_old_last(index);
293    }
294
295    pub(crate) fn swap_remove_and_get_old_last(&mut self, index: usize) -> Option<T> {
296        let last_item_index = self.len();
297        if index == 0 || index > last_item_index {
298            SA::error_api_impl().signal_error(&b"index out of range"[..]);
299        }
300
301        let mut last_item_as_option = Option::None;
302        if index != last_item_index {
303            let last_item = self.get(last_item_index);
304            self.set(index, &last_item);
305            last_item_as_option = Some(last_item);
306        }
307        self.clear_entry(last_item_index);
308        self.save_count(last_item_index - 1);
309        last_item_as_option
310    }
311
312    /// Deletes all contents form storage and sets count to 0.
313    /// Can easily consume a lot of gas.
314    pub fn clear(&mut self) {
315        let len = self.len();
316        for i in 1..=len {
317            storage_clear(self.item_key(i).as_ref());
318        }
319        self.save_count(0);
320    }
321}
322
323impl<'a, SA, T, A> IntoIterator for &'a VecMapper<SA, T, A>
324where
325    SA: StorageMapperApi,
326    A: StorageAddress<SA>,
327    T: TopEncode + TopDecode + 'static,
328{
329    type Item = T;
330
331    type IntoIter = Iter<'a, SA, T, A>;
332
333    fn into_iter(self) -> Self::IntoIter {
334        self.iter()
335    }
336}
337
338/// An iterator over the elements of a `VecMapper`.
339///
340/// This `struct` is created by [`VecMapper::iter()`]. See its
341/// documentation for more.
342pub struct Iter<'a, SA, T, A>
343where
344    SA: StorageMapperApi,
345    A: StorageAddress<SA>,
346    T: TopEncode + TopDecode + 'static,
347{
348    index: usize,
349    len: usize,
350    vec: &'a VecMapper<SA, T, A>,
351}
352
353impl<'a, SA, T, A> Iter<'a, SA, T, A>
354where
355    SA: StorageMapperApi,
356    A: StorageAddress<SA>,
357    T: TopEncode + TopDecode + 'static,
358{
359    fn new(vec: &'a VecMapper<SA, T, A>) -> Iter<'a, SA, T, A> {
360        Iter {
361            index: 1,
362            len: vec.len(),
363            vec,
364        }
365    }
366}
367
368impl<SA, T, A> Iterator for Iter<'_, SA, T, A>
369where
370    SA: StorageMapperApi,
371    A: StorageAddress<SA>,
372    T: TopEncode + TopDecode + 'static,
373{
374    type Item = T;
375
376    #[inline]
377    fn next(&mut self) -> Option<T> {
378        let current_index = self.index;
379        if current_index > self.len {
380            return None;
381        }
382        self.index += 1;
383        Some(self.vec.get_unchecked(current_index))
384    }
385}
386
387/// Behaves like a MultiResultVec when an endpoint result.
388impl<SA, T> TopEncodeMulti for VecMapper<SA, T, CurrentStorage>
389where
390    SA: StorageMapperApi,
391    T: TopEncode + TopDecode,
392{
393    fn multi_encode_or_handle_err<O, H>(&self, output: &mut O, h: H) -> Result<(), H::HandledErr>
394    where
395        O: TopEncodeMultiOutput,
396        H: EncodeErrorHandler,
397    {
398        multi_encode_iter_or_handle_err(self.iter(), output, h)
399    }
400}
401
402impl<SA, T> TypeAbiFrom<VecMapper<SA, T, CurrentStorage>> for MultiValueEncoded<SA, T>
403where
404    SA: StorageMapperApi,
405    T: TopEncode + TopDecode,
406{
407}
408
409impl<SA, T> TypeAbiFrom<Self> for VecMapper<SA, T, CurrentStorage>
410where
411    SA: StorageMapperApi,
412    T: TopEncode + TopDecode,
413{
414}
415
416/// Behaves like a MultiResultVec when an endpoint result.
417impl<SA, T> TypeAbi for VecMapper<SA, T, CurrentStorage>
418where
419    SA: StorageMapperApi,
420    T: TopEncode + TopDecode + TypeAbi,
421{
422    type Unmanaged = Self;
423
424    fn type_name() -> TypeName {
425        crate::abi::type_name_variadic::<T>()
426    }
427
428    fn type_name_rust() -> TypeName {
429        crate::abi::type_name_multi_value_encoded::<T>()
430    }
431
432    fn provide_type_descriptions<TDC: TypeDescriptionContainer>(accumulator: &mut TDC) {
433        T::provide_type_descriptions(accumulator);
434    }
435
436    fn is_variadic() -> bool {
437        true
438    }
439}