Skip to main content
Version: Next

Calimero Collections Reference

This comprehensive reference covers all available collections in the Calimero SDK, their performance characteristics, and best practices for using them effectively.

Table of Contents​

Overview​

Calimero collections are specifically designed for WebAssembly and decentralized applications. They provide:

  • Wasm Optimization: Designed for efficient serialization and memory usage
  • Persistent Storage: Automatic integration with Calimero's storage system
  • Type Safety: Full Rust type safety with compile-time guarantees
  • Performance: Optimized for common operations in decentralized apps
  • Error Handling: All operations return Result<T, StoreError> for robust error handling

Core Collections​

UnorderedMap​

A hash map implementation optimized for Wasm with O(1) average case operations. All operations return Result<T, StoreError>.

Basic Usage​

use calimero_storage::collections::UnorderedMap;

#[app::state]
#[derive(Default, BorshSerialize, BorshDeserialize)]
#[borsh(crate = "calimero_sdk::borsh")]
struct UserRegistry {
users: UnorderedMap<String, UserProfile>,
user_count: u32,
}

#[app::logic]
impl UserRegistry {
pub fn add_user(&mut self, username: String, profile: UserProfile) -> app::Result<()> {
if self.users.contains_key(&username)? {
return app::bail!("User already exists");
}

self.users.insert(username, profile)?;
self.user_count += 1;
Ok(())
}

pub fn get_user(&self, username: &str) -> app::Result<Option<UserProfile>> {
self.users.get(username).map_err(Into::into)
}

pub fn update_user(&mut self, username: String, profile: UserProfile) -> app::Result<()> {
if !self.users.contains_key(&username)? {
return app::bail!("User not found");
}

self.users.insert(username, profile)?;
Ok(())
}

pub fn remove_user(&mut self, username: &str) -> app::Result<Option<UserProfile>> {
let user = self.users.remove(username)?;
if user.is_some() {
self.user_count -= 1;
}
Ok(user)
}
}

Advanced Operations​

#[app::logic]
impl UserRegistry {
// Batch operations
pub fn add_multiple_users(&mut self, users: Vec<(String, UserProfile)>) -> app::Result<()> {
for (username, profile) in users {
if self.users.contains_key(&username)? {
return app::bail!("User {} already exists", username);
}
self.users.insert(username, profile)?;
self.user_count += 1;
}
Ok(())
}

// Iteration
pub fn get_all_users(&self) -> app::Result<Vec<UserProfile>> {
self.users.entries()?.collect()
}

// Search
pub fn search_users(&self, query: &str) -> app::Result<Vec<UserProfile>> {
Ok(self.users
.entries()?
.filter(|(username, _)| username.contains(query))
.map(|(_, profile)| profile)
.collect())
}

// Statistics
pub fn get_user_count(&self) -> app::Result<usize> {
self.users.len()
}
}

UnorderedSet​

A hash set for unique elements with similar performance characteristics to UnorderedMap.

use calimero_storage::collections::UnorderedSet;

#[app::state]
#[derive(Default, BorshSerialize, BorshDeserialize)]
#[borsh(crate = "calimero_sdk::borsh")]
struct AccessControl {
admins: UnorderedSet<String>,
moderators: UnorderedSet<String>,
}

#[app::logic]
impl AccessControl {
pub fn add_admin(&mut self, username: String) -> app::Result<()> {
self.admins.insert(username)?;
Ok(())
}

pub fn is_admin(&self, username: &str) -> app::Result<bool> {
self.admins.contains(username)
}

pub fn remove_admin(&mut self, username: &str) -> app::Result<bool> {
self.admins.remove(username)
}

pub fn get_admin_count(&self) -> app::Result<usize> {
self.admins.len()
}

pub fn get_all_admins(&self) -> app::Result<Vec<String>> {
self.admins.entries()?.collect()
}
}

Vector​

A dynamic array implementation optimized for sequential access.

use calimero_storage::collections::Vector;

#[app::state]
#[derive(Default, BorshSerialize, BorshDeserialize)]
#[borsh(crate = "calimero_sdk::borsh")]
struct TaskManager {
tasks: Vector<Task>,
completed_tasks: Vector<Task>,
}

#[app::logic]
impl TaskManager {
pub fn add_task(&mut self, task: Task) -> app::Result<()> {
self.tasks.insert(None, task)?;
Ok(())
}

pub fn complete_task(&mut self, index: u32) -> app::Result<()> {
if index >= self.tasks.len()? {
return app::bail!("Invalid task index");
}

let task = self.tasks.remove(index as usize)?;
self.completed_tasks.insert(None, task)?;
Ok(())
}

pub fn get_task(&self, index: u32) -> app::Result<Option<Task>> {
if index >= self.tasks.len()? {
return Ok(None);
}
self.tasks.get(index as usize)
}

pub fn get_all_tasks(&self) -> app::Result<Vec<Task>> {
self.tasks.entries()?.collect()
}
}

Specialized Collections​

Note: Some of these specialized collections may be planned for future releases or available in different versions of the SDK. Check the latest SDK documentation for current availability.

LookupMap​

A map optimized for cases where you need to iterate over keys or values frequently. This collection provides better iteration performance at the cost of slightly higher memory usage.

use calimero_storage::collections::LookupMap;

#[app::state]
#[derive(Default, BorshSerialize, BorshDeserialize)]
#[borsh(crate = "calimero_sdk::borsh")]
struct Analytics {
// User -> Page views
page_views: LookupMap<String, u32>,
// Page -> Total views
total_views: LookupMap<String, u64>,
}

#[app::logic]
impl Analytics {
pub fn record_page_view(&mut self, user: String, page: String) -> app::Result<()> {
// Increment user's page views
let user_views = self.page_views.get(&user)?.unwrap_or(0);
self.page_views.insert(user, user_views + 1)?;

// Increment total page views
let total = self.total_views.get(&page)?.unwrap_or(0);
self.total_views.insert(page, total + 1)?;

Ok(())
}

pub fn get_user_stats(&self, user: &str) -> app::Result<u32> {
Ok(self.page_views.get(user)?.unwrap_or(0))
}

pub fn get_page_stats(&self, page: &str) -> app::Result<u64> {
Ok(self.total_views.get(page)?.unwrap_or(0))
}

pub fn get_top_pages(&self, limit: usize) -> app::Result<Vec<(String, u64)>> {
let mut pages: Vec<(String, u64)> = self.total_views
.entries()?
.map(|(page, views)| (page, views))
.collect();

pages.sort_by(|a, b| b.1.cmp(&a.1));
pages.truncate(limit);
Ok(pages)
}
}

TreeMap​

A sorted map implementation for cases where you need ordered keys. This collection maintains keys in sorted order, enabling efficient range queries and ordered iteration.

use calimero_storage::collections::TreeMap;

#[app::state]
#[derive(Default, BorshSerialize, BorshDeserialize)]
#[borsh(crate = "calimero_sdk::borsh")]
struct Leaderboard {
// Score -> List of users with that score
score_rankings: TreeMap<u32, UnorderedSet<String>>,
// User -> Current score
user_scores: UnorderedMap<String, u32>,
}

#[app::logic]
impl Leaderboard {
pub fn update_score(&mut self, user: String, new_score: u32) -> app::Result<()> {
// Remove user from old score ranking
if let Some(old_score) = self.user_scores.get(&user)? {
if let Some(users_at_score) = self.score_rankings.get(&old_score)? {
let mut updated_users = users_at_score.clone();
updated_users.remove(&user)?;

if updated_users.is_empty() {
self.score_rankings.remove(&old_score)?;
} else {
self.score_rankings.insert(old_score, updated_users)?;
}
}
}

// Add user to new score ranking
let mut users_at_score = self.score_rankings
.get(&new_score)?
.unwrap_or_else(|| UnorderedSet::new());
users_at_score.insert(user.clone())?;
self.score_rankings.insert(new_score, users_at_score)?;

// Update user's score
self.user_scores.insert(user, new_score)?;

Ok(())
}

pub fn get_top_players(&self, limit: usize) -> app::Result<Vec<(String, u32)>> {
let mut top_players = Vec::new();

// Iterate in reverse order (highest scores first)
for (score, users) in self.score_rankings.entries()?.rev() {
for user in users.entries()? {
top_players.push((user, score));
if top_players.len() >= limit {
break;
}
}
if top_players.len() >= limit {
break;
}
}

Ok(top_players)
}

pub fn get_user_rank(&self, user: &str) -> app::Result<Option<u32>> {
let user_score = match self.user_scores.get(user)? {
Some(score) => score,
None => return Ok(None),
};

let mut rank = 1;

// Count users with higher scores
for (score, users) in self.score_rankings.entries()?.rev() {
if score > user_score {
rank += users.len()? as u32;
} else {
break;
}
}

Ok(Some(rank))
}
}

When to Use Each Collection​

Use UnorderedMap when:​

  • You need fast key-value lookups
  • Order doesn't matter
  • Memory efficiency is important

Use UnorderedSet when:​

  • You need to track unique elements
  • Fast membership testing is required
  • Order doesn't matter

Use Vector when:​

  • You need ordered elements
  • Sequential access is common
  • You frequently add/remove from the end

Use LookupMap when:​

  • You frequently iterate over keys or values
  • Memory overhead is acceptable
  • You need both map and iteration performance

Use TreeMap when:​

  • You need sorted keys
  • Range queries are common
  • Order matters for your use case

Performance Characteristics​

Operation Complexity​

OperationUnorderedMapUnorderedSetVector
InsertO(1) avgO(1) avgO(1)
GetO(1) avgO(1) avgO(1)
RemoveO(1) avgO(1) avgO(n)
IterateO(n)O(n)O(n)
ContainsO(1) avgO(1) avgO(n)

Memory Usage​

  • UnorderedMap/UnorderedSet: Hash table overhead + key-value storage
  • Vector: Dynamic array with growth factor
  • All collections automatically handle serialization/deserialization

Collection Patterns​

Entry API for Conditional Operations​

#[app::logic]
impl UserRegistry {
pub fn get_or_create_user(&mut self, username: String) -> app::Result<&mut UserProfile> {
// This pattern doesn't work with current API - collections return Result
// Use conditional logic instead
if !self.users.contains_key(&username)? {
let default_profile = UserProfile::default();
self.users.insert(username.clone(), default_profile)?;
}

// Note: We can't return &mut due to Result wrapper
// Consider returning the value or using a different pattern
Ok(())
}
}

Batch Operations​

#[app::logic]
impl UserRegistry {
pub fn bulk_update_users(&mut self, updates: Vec<(String, UserProfile)>) -> app::Result<()> {
for (username, profile) in updates {
self.users.insert(username, profile)?;
}
Ok(())
}

pub fn bulk_remove_users(&mut self, usernames: Vec<String>) -> app::Result<usize> {
let mut removed_count = 0;
for username in usernames {
if self.users.remove(&username)?.is_some() {
removed_count += 1;
}
}
Ok(removed_count)
}
}

Best Practices​

Error Handling​

Always handle the Result returned by collection operations:

// ❌ Bad - ignoring errors
let value = self.users.get(&key).unwrap();

// ✅ Good - proper error handling
let value = match self.users.get(&key)? {
Some(v) => v,
None => return app::bail!("User not found"),
};

Memory Management​

// ❌ Bad - creating unnecessary clones
let all_users: Vec<String> = self.users.entries()?
.map(|(k, _)| k.clone())
.collect();

// ✅ Good - avoid unnecessary cloning
let all_users: Vec<&String> = self.users.entries()?
.map(|(k, _)| k)
.collect();

Transaction Safety​

#[app::logic]
impl UserRegistry {
pub fn transfer_user(&mut self, from: String, to: String) -> app::Result<()> {
// All operations are atomic - if any fail, all changes are rolled back
let profile = self.users.remove(&from)?
.ok_or_else(|| app::err!("Source user not found"))?;

self.users.insert(to, profile)?;
Ok(())
}
}

Migration from Standard Collections​

Key Differences​

Standard CollectionCalimero CollectionKey Changes
HashMap<K, V>UnorderedMap<K, V>Returns Result<T, StoreError>
HashSet<T>UnorderedSet<T>Returns Result<T, StoreError>
Vec<T>Vector<T>Returns Result<T, StoreError>

Migration Example​

// Before (standard collections)
use std::collections::HashMap;

struct OldApp {
users: HashMap<String, UserProfile>,
}

impl OldApp {
pub fn add_user(&mut self, username: String, profile: UserProfile) {
self.users.insert(username, profile);
}
}

// After (Calimero collections)
use calimero_storage::collections::UnorderedMap;

#[app::state]
#[derive(Default, BorshSerialize, BorshDeserialize)]
#[borsh(crate = "calimero_sdk::borsh")]
struct NewApp {
users: UnorderedMap<String, UserProfile>,
}

#[app::logic]
impl NewApp {
pub fn add_user(&mut self, username: String, profile: UserProfile) -> app::Result<()> {
self.users.insert(username, profile)?;
Ok(())
}
}

Common Pitfalls​

Forgetting Error Handling​

// ❌ This will not compile
let user = self.users.get(&username).unwrap();

// ✅ Handle the Result
let user = self.users.get(&username)?;

Assuming Standard Collection Methods​

// ❌ Standard collections don't have these methods
let count = self.users.len(); // Standard collections return usize directly

// ✅ Calimero collections return Result
let count = self.users.len()?; // Returns Result<usize, StoreError>

Ignoring Storage Errors​

// ❌ Bad - storage errors can indicate serious issues
if let Ok(user) = self.users.get(&username) {
// Handle user
}

// ✅ Good - propagate errors up
let user = self.users.get(&username)?;

Next Steps​

Was this page helpful?
Need some help? Check Support page