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