Rust SDK Deep Dive
This comprehensive guide covers advanced usage of the Calimero Rust SDK, including collections, events, macros, and best practices for building production applications.
Table of Contents
- Collections
- Events and Event Handling
- Advanced Macros
- State Management Patterns
- Performance Optimization
- Testing and Debugging
- Common Patterns and Anti-patterns
Collections
Calimero provides a rich set of collections optimized for WebAssembly and
decentralized applications. These collections are designed to work efficiently
with the Calimero storage system and return Result<T, StoreError>
for robust
error handling.
Core Collections
UnorderedMap
A hash map implementation optimized for Wasm with O(1) average case operations:
use calimero_storage::collections::UnorderedMap;
#[app::state]
#[derive(Default, BorshSerialize, BorshDeserialize)]
#[borsh(crate = "calimero_sdk::borsh")]
struct MyApp {
users: UnorderedMap<String, UserProfile>,
settings: UnorderedMap<String, String>,
}
#[app::logic]
impl MyApp {
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)?;
Ok(())
}
pub fn get_user(&self, username: &str) -> app::Result<Option<UserProfile>> {
self.users.get(username).map_err(Into::into)
}
pub fn remove_user(&mut self, username: &str) -> app::Result<Option<UserProfile>> {
self.users.remove(username)
}
pub fn get_user_count(&self) -> app::Result<usize> {
self.users.len()
}
}
UnorderedSet
A hash set for unique elements:
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()
}
}
Vector
A dynamic array implementation:
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()
}
}
Advanced Collection Patterns
Nested Collections
#[app::state]
#[derive(Default, BorshSerialize, BorshDeserialize)]
#[borsh(crate = "calimero_sdk::borsh")]
struct SocialNetwork {
// User -> List of friends
friendships: UnorderedMap<String, UnorderedSet<String>>,
// User -> List of posts
user_posts: UnorderedMap<String, Vector<Post>>,
// Post -> List of likes
post_likes: UnorderedMap<u64, UnorderedSet<String>>,
}
#[app::logic]
impl SocialNetwork {
pub fn add_friend(&mut self, user: String, friend: String) -> app::Result<()> {
// Add bidirectional friendship
self.friendships
.entry(user.clone())
.or_insert_with(|| UnorderedSet::new())
.insert(friend.clone())?;
self.friendships
.entry(friend)
.or_insert_with(|| UnorderedSet::new())
.insert(user)?;
Ok(())
}
pub fn get_friends(&self, user: &str) -> app::Result<Vec<String>> {
Ok(self.friendships
.get(user)?
.map(|friends| friends.entries()?.collect())
.unwrap_or_default())
}
pub fn add_post(&mut self, user: String, post: Post) -> app::Result<()> {
self.user_posts
.entry(user.clone())
.or_insert_with(|| Vector::new())
.insert(None, post)?;
Ok(())
}
}
Collection Iterators
#[app::logic]
impl MyApp {
pub fn get_all_users(&self) -> app::Result<Vec<UserProfile>> {
self.users.entries()?.collect()
}
pub fn search_users(&self, query: &str) -> app::Result<Vec<UserProfile>> {
Ok(self.users
.entries()?
.filter(|(username, _)| username.contains(query))
.map(|(_, profile)| profile)
.collect())
}
pub fn get_active_users(&self) -> app::Result<Vec<UserProfile>> {
Ok(self.users
.entries()?
.filter(|(_, profile)| profile.is_active)
.map(|(_, profile)| profile)
.collect())
}
}
Events and Event Handling
Event Definition
Events in Calimero use lifetime parameters for efficient string handling:
#[app::event]
pub enum AppEvent<'a> {
UserCreated { username: &'a str, timestamp: u64 },
UserUpdated { username: &'a str, field: &'a str },
UserDeleted { username: &'a str },
SettingsChanged { key: &'a str, old_value: &'a str, new_value: &'a str },
}
Event Emission
Events are emitted using the app::emit!
macro and are only recorded if the
transaction succeeds:
#[app::logic]
impl MyApp {
pub fn create_user(&mut self, username: String, profile: UserProfile) -> app::Result<()> {
app::log!("Creating user: {}", username);
if self.users.contains_key(&username)? {
return app::bail!("User already exists");
}
self.users.insert(username.clone(), profile)?;
app::emit!(AppEvent::UserCreated {
username: &username,
timestamp: env::block_timestamp(),
});
Ok(())
}
pub fn update_user(&mut self, username: String, new_profile: UserProfile) -> app::Result<()> {
app::log!("Updating user: {}", username);
let old_profile = self.users.get(&username)?
.ok_or_else(|| app::err!("User not found"))?;
self.users.insert(username.clone(), new_profile)?;
app::emit!(AppEvent::UserUpdated {
username: &username,
field: "profile",
});
Ok(())
}
}
Event Patterns
Batch Events
#[app::logic]
impl MyApp {
pub fn bulk_create_users(&mut self, users: Vec<(String, UserProfile)>) -> app::Result<()> {
let mut created_count = 0;
for (username, profile) in users {
if !self.users.contains_key(&username)? {
self.users.insert(username.clone(), profile)?;
created_count += 1;
app::emit!(AppEvent::UserCreated {
username: &username,
timestamp: env::block_timestamp(),
});
}
}
app::log!("Created {} new users", created_count);
Ok(())
}
}
Conditional Events
#[app::logic]
impl MyApp {
pub fn update_user_if_changed(&mut self, username: String, new_profile: UserProfile) -> app::Result<bool> {
let old_profile = match self.users.get(&username)? {
Some(profile) => profile,
None => return Ok(false),
};
if old_profile == new_profile {
return Ok(false); // No change, no event
}
self.users.insert(username.clone(), new_profile)?;
app::emit!(AppEvent::UserUpdated {
username: &username,
field: "profile",
});
Ok(true) // Changed
}
}
Advanced Macros
Macro Composition
Macros can be combined for complex functionality:
#[app::state(emits = for<'a> AppEvent<'a>)]
#[derive(Default, BorshSerialize, BorshDeserialize)]
#[borsh(crate = "calimero_sdk::borsh")]
struct AdvancedApp {
users: UnorderedMap<String, UserProfile>,
audit_log: Vector<AuditEntry>,
}
#[app::logic]
impl AdvancedApp {
#[app::init]
pub fn init() -> Self {
Self {
users: UnorderedMap::new(),
audit_log: Vector::new(),
}
}
pub fn perform_action(&mut self, action: Action) -> app::Result<()> {
// Log the action
let entry = AuditEntry {
action: action.clone(),
timestamp: env::block_timestamp(),
user: env::predecessor_account_id().to_string(),
};
self.audit_log.insert(None, entry)?;
// Perform the action
match action {
Action::CreateUser { username, profile } => {
self.create_user(username, profile)?;
}
Action::UpdateUser { username, profile } => {
self.update_user(username, profile)?;
}
Action::DeleteUser { username } => {
self.delete_user(username)?;
}
}
Ok(())
}
}
Custom Error Types
#[derive(Debug, thiserror::Error)]
pub enum AppError {
#[error("User not found: {0}")]
UserNotFound(String),
#[error("Invalid operation: {0}")]
InvalidOperation(String),
#[error("Insufficient permissions")]
InsufficientPermissions,
}
impl From<AppError> for calimero_sdk::types::Error {
fn from(err: AppError) -> Self {
calimero_sdk::types::Error::msg(err.to_string())
}
}
#[app::logic]
impl MyApp {
pub fn admin_only_operation(&mut self, username: String) -> app::Result<()> {
if !self.is_admin(&env::predecessor_account_id().to_string())? {
return Err(AppError::InsufficientPermissions.into());
}
// Perform admin operation
Ok(())
}
}
Advanced Macro Features
Note: Some of these advanced macro features may be planned for future releases or available in different versions of the SDK. Check the latest SDK documentation for current availability.
Custom State Configuration
Advanced state configuration with metadata and versioning:
#[app::state(
emits = for<'a> AppEvent<'a>,
version = "1.0.0",
name = "UserManagementApp"
)]
#[derive(Default, BorshSerialize, BorshDeserialize)]
#[borsh(crate = "calimero_sdk::borsh")]
struct UserManagementApp {
users: UnorderedMap<String, UserProfile>,
settings: AppSettings,
}
#[derive(BorshSerialize, BorshDeserialize, Default)]
struct AppSettings {
max_users: u32,
allow_public_profiles: bool,
maintenance_mode: bool,
}
#[app::logic]
impl UserManagementApp {
pub fn create_user(&mut self, username: String, profile: UserProfile) -> app::Result<()> {
// Check maintenance mode
if self.settings.maintenance_mode {
return app::bail!("Application is in maintenance mode");
}
// Check user limit
if self.users.len()? >= self.settings.max_users {
return app::bail!("Maximum user limit reached");
}
if self.users.contains_key(&username)? {
return app::bail!("User already exists");
}
self.users.insert(username.clone(), profile)?;
app::emit!(AppEvent::UserCreated {
username: &username,
timestamp: env::block_timestamp(),
});
Ok(())
}
pub fn update_settings(&mut self, new_settings: AppSettings) -> app::Result<()> {
// Validate settings
if new_settings.max_users < self.users.len()? as u32 {
return app::bail!("Cannot reduce max users below current user count");
}
self.settings = new_settings;
Ok(())
}
}
Access Control Patterns
Note: While these specific macros may not be implemented, the patterns shown here demonstrate how to implement access control using the available SDK features.
#[app::logic]
impl UserManagementApp {
// Admin-only operations
pub fn admin_only_operation(&mut self, username: String) -> app::Result<()> {
if !self.is_admin(&env::predecessor_account_id().to_string())? {
return app::bail!("Admin access required");
}
// Perform admin operation
Ok(())
}
// Moderator or admin operations
pub fn moderator_or_admin_operation(&mut self, username: String) -> app::Result<()> {
let caller = env::predecessor_account_id().to_string();
if !self.is_admin(&caller)? && !self.is_moderator(&caller)? {
return app::bail!("Moderator or admin access required");
}
// Perform operation
Ok(())
}
// Role-based access control
pub fn role_based_operation(&mut self, username: String, required_role: UserRole) -> app::Result<()> {
let caller = env::predecessor_account_id().to_string();
let caller_role = self.get_user_role(&caller)?;
if !self.has_permission(caller_role, required_role)? {
return app::bail!("Insufficient permissions for this operation");
}
// Perform operation
Ok(())
}
fn has_permission(&self, user_role: UserRole, required_role: UserRole) -> app::Result<bool> {
match (user_role, required_role) {
(UserRole::Admin, _) => Ok(true), // Admin can do everything
(UserRole::Moderator, UserRole::Moderator | UserRole::User) => Ok(true),
(UserRole::User, UserRole::User) => Ok(true),
_ => Ok(false),
}
}
}
Conditional Logic and State Management
Advanced patterns for conditional operations and state transitions:
#[app::logic]
impl UserManagementApp {
pub fn conditional_user_operation(&mut self, username: String, operation: UserOperation) -> app::Result<()> {
// Check application state
if self.settings.maintenance_mode {
return app::bail!("Application is in maintenance mode");
}
// Check user state
let user = self.users.get(&username)?
.ok_or_else(|| app::err!("User not found"))?;
if !user.is_active {
return app::bail!("User account is not active");
}
// Perform operation based on user role
match operation {
UserOperation::UpdateProfile(profile) => {
if !self.can_update_profile(&username, &profile)? {
return app::bail!("Cannot update profile with current permissions");
}
self.users.insert(username, profile)?;
}
UserOperation::ChangeRole(new_role) => {
if !self.can_change_role(&username, new_role)? {
return app::bail!("Cannot change role with current permissions");
}
let mut updated_user = user.clone();
updated_user.role = new_role;
self.users.insert(username, updated_user)?;
}
UserOperation::Suspend(reason) => {
if !self.can_suspend_user(&username)? {
return app::bail!("Cannot suspend user with current permissions");
}
let mut updated_user = user.clone();
updated_user.status = UserStatus::Suspended(reason);
self.users.insert(username, updated_user)?;
}
}
Ok(())
}
fn can_update_profile(&self, username: &str, profile: &UserProfile) -> app::Result<bool> {
let caller = env::predecessor_account_id().to_string();
// Users can update their own profile
if caller == *username {
return Ok(true);
}
// Admins can update any profile
if self.is_admin(&caller)? {
return Ok(true);
}
// Check if profile is public and caller has permission
if profile.is_public && self.settings.allow_public_profiles {
return Ok(true);
}
Ok(false)
}
}
State Management Patterns
Immutable State Access
#[app::logic]
impl MyApp {
pub fn get_user_stats(&self) -> app::Result<UserStats> {
let mut stats = UserStats::default();
for (_, profile) in self.users.entries()? {
stats.total_users += 1;
if profile.is_active {
stats.active_users += 1;
}
if profile.is_premium {
stats.premium_users += 1;
}
}
Ok(stats)
}
}
Mutable State with Validation
#[app::logic]
impl MyApp {
pub fn transfer_user(&mut self, from: String, to: String) -> app::Result<()> {
// Validate input
if from == to {
return app::bail!("Cannot transfer user to same account");
}
// Check source user exists
let profile = self.users.get(&from)?
.ok_or_else(|| app::err!("Source user not found"))?;
// Check destination doesn't exist
if self.users.contains_key(&to)? {
return app::bail!("Destination user already exists");
}
// Perform transfer atomically
self.users.remove(&from)?;
self.users.insert(to, profile)?;
app::emit!(AppEvent::UserTransferred {
from: &from,
to: &to,
});
Ok(())
}
}
State Initialization Patterns
#[app::logic]
impl MyApp {
#[app::init]
pub fn init() -> Self {
Self {
users: UnorderedMap::new(),
settings: UnorderedMap::new(),
metadata: AppMetadata {
version: "1.0.0".to_string(),
created_at: env::block_timestamp(),
},
}
}
#[app::init]
pub fn init_with_config(config: AppConfig) -> Self {
let mut app = Self::init();
// Apply configuration
for (key, value) in config.default_settings {
app.settings.insert(key, value).expect("Failed to set default");
}
app
}
}
Performance Optimization
Efficient Iteration
#[app::logic]
impl MyApp {
// ✅ Good: Use references to avoid cloning
pub fn get_active_users(&self) -> app::Result<Vec<&UserProfile>> {
Ok(self.users
.entries()?
.filter(|(_, profile)| profile.is_active)
.map(|(_, profile)| profile)
.collect())
}
// ❌ Bad: Unnecessary cloning
pub fn get_active_users_bad(&self) -> app::Result<Vec<UserProfile>> {
Ok(self.users
.entries()?
.filter(|(_, profile)| profile.is_active)
.map(|(_, profile)| profile.clone()) // Unnecessary clone
.collect())
}
}
Batch Operations
#[app::logic]
impl MyApp {
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)
}
}
Memory Management
#[app::logic]
impl MyApp {
pub fn cleanup_old_data(&mut self, cutoff_timestamp: u64) -> app::Result<usize> {
let mut to_remove = Vec::new();
// Collect items to remove
for (key, profile) in self.users.entries()? {
if profile.last_activity < cutoff_timestamp {
to_remove.push(key);
}
}
// Remove old items
let mut removed_count = 0;
for key in to_remove {
if self.users.remove(&key)?.is_some() {
removed_count += 1;
}
}
app::log!("Cleaned up {} old user profiles", removed_count);
Ok(removed_count)
}
}
Testing and Debugging
Logging Best Practices
#[app::logic]
impl MyApp {
pub fn complex_operation(&mut self, input: ComplexInput) -> app::Result<ComplexOutput> {
app::log!("Starting complex operation with input: {:?}", input);
// Step 1: Validation
app::log!("Step 1: Validating input");
self.validate_input(&input)?;
// Step 2: Processing
app::log!("Step 2: Processing input");
let intermediate = self.process_input(input)?;
// Step 3: Finalization
app::log!("Step 3: Finalizing operation");
let result = self.finalize_operation(intermediate)?;
app::log!("Complex operation completed successfully");
Ok(result)
}
}
Error Context
#[app::logic]
impl MyApp {
pub fn get_user_with_context(&self, username: &str) -> app::Result<UserProfile> {
self.users.get(username)
.map_err(|e| {
app::log!("Failed to get user '{}': {:?}", username, e);
e
})?
.ok_or_else(|| {
app::log!("User '{}' not found", username);
app::err!("User not found: {}", username)
})
}
}
Unit Testing
Note: While the Calimero SDK doesn't provide built-in testing macros, you can use standard Rust testing approaches. Here are examples of how to structure tests for your Calimero applications.
#[cfg(test)]
mod tests {
use super::*;
use calimero_storage::collections::UnorderedMap;
// Mock storage for testing
struct MockStorage;
impl StorageAdaptor for MockStorage {
// Implement mock storage methods for testing
}
#[test]
fn test_user_creation() {
let mut app = UserManagementApp::default();
let profile = UserProfile::default();
let result = app.create_user("test_user".to_string(), profile);
assert!(result.is_ok());
assert!(app.users.contains_key("test_user").unwrap());
}
#[test]
fn test_duplicate_user_creation() {
let mut app = UserManagementApp::default();
let profile = UserProfile::default();
app.create_user("test_user".to_string(), profile.clone()).unwrap();
let result = app.create_user("test_user".to_string(), profile);
assert!(result.is_err());
}
#[test]
fn test_user_update() {
let mut app = UserManagementApp::default();
let profile = UserProfile::default();
let updated_profile = UserProfile {
name: "Updated Name".to_string(),
..profile.clone()
};
app.create_user("test_user".to_string(), profile).unwrap();
let result = app.update_user("test_user".to_string(), updated_profile);
assert!(result.is_ok());
}
}
Debug Logging and State Inspection
#[app::logic]
impl MyApp {
pub fn debug_state(&self) -> app::Result<String> {
let user_count = self.users.len()?;
let active_users = self.users.entries()?
.filter(|(_, profile)| profile.is_active)
.count();
let debug_info = format!(
"Users: {}, Active: {}, Settings: {:?}",
user_count,
active_users,
self.settings
);
app::log!("Debug state: {}", debug_info);
Ok(debug_info)
}
pub fn validate_integrity(&self) -> app::Result<Vec<String>> {
let mut issues = Vec::new();
// Check for orphaned references
for (username, profile) in self.users.entries()? {
if let Some(referenced_user) = &profile.referenced_by {
if !self.users.contains_key(referenced_user)? {
issues.push(format!("User '{}' references non-existent user '{}'", username, referenced_user));
}
}
}
// Check for circular references
// ... implementation details ...
if !issues.is_empty() {
app::log!("Integrity check found {} issues: {:?}", issues.len(), issues);
}
Ok(issues)
}
}
Performance Monitoring
#[app::logic]
impl MyApp {
pub fn performance_metrics(&self) -> app::Result<PerformanceMetrics> {
let start_time = std::time::Instant::now();
let user_count = self.users.len()?;
let storage_size = self.estimate_storage_size()?;
let operation_count = self.get_operation_count()?;
let metrics = PerformanceMetrics {
user_count,
storage_size,
operation_count,
response_time: start_time.elapsed().as_millis() as u64,
};
app::log!("Performance metrics: {:?}", metrics);
Ok(metrics)
}
fn estimate_storage_size(&self) -> app::Result<usize> {
// Estimate storage size based on collection contents
let mut total_size = 0;
for (_, profile) in self.users.entries()? {
total_size += std::mem::size_of_val(&profile);
}
Ok(total_size)
}
}
#[derive(Debug)]
struct PerformanceMetrics {
user_count: usize,
storage_size: usize,
operation_count: u64,
response_time: u64,
}
Common Patterns and Anti-patterns
✅ Good Patterns
Atomic Operations
#[app::logic]
impl MyApp {
pub fn atomic_user_update(&mut self, username: String, updates: UserUpdates) -> app::Result<()> {
// All operations are atomic - if any fail, all changes are rolled back
let mut profile = self.users.get(&username)?
.ok_or_else(|| app::err!("User not found"))?;
// Apply updates
if let Some(name) = updates.name {
profile.name = name;
}
if let Some(email) = updates.email {
profile.email = email;
}
// Save updated profile
self.users.insert(username, profile)?;
app::emit!(AppEvent::UserUpdated {
username: &username,
field: "profile",
});
Ok(())
}
}
Proper Error Handling
#[app::logic]
impl MyApp {
pub fn safe_user_operation(&mut self, username: &str) -> app::Result<()> {
// Handle all possible error cases
match self.users.get(username)? {
Some(profile) => {
if profile.is_active {
// Perform operation
Ok(())
} else {
app::bail!("User is not active")
}
}
None => app::bail!("User not found"),
}
}
}
❌ Anti-patterns
Ignoring Errors
// ❌ Bad: Ignoring collection operation results
pub fn bad_user_operation(&mut self, username: String) {
self.users.insert(username, UserProfile::default()); // Ignoring Result
}
// ✅ Good: Handle errors properly
pub fn good_user_operation(&mut self, username: String) -> app::Result<()> {
self.users.insert(username, UserProfile::default())?;
Ok(())
}
Unnecessary Cloning
// ❌ Bad: Cloning when references would suffice
pub fn bad_get_users(&self) -> app::Result<Vec<String>> {
Ok(self.users.entries()?
.map(|(k, _)| k.clone()) // Unnecessary clone
.collect())
}
// ✅ Good: Use references
pub fn good_get_users(&self) -> app::Result<Vec<&String>> {
Ok(self.users.entries()?
.map(|(k, _)| k) // No clone needed
.collect())
}
Complex Nested Operations
// ❌ Bad: Deep nesting makes error handling complex
pub fn bad_nested_operation(&mut self, user_id: &str) -> app::Result<()> {
if let Some(user) = self.users.get(user_id)? {
if let Some(profile) = user.profile {
if let Some(settings) = profile.settings {
// Deep nesting makes this hard to read and maintain
settings.update()?;
}
}
}
Ok(())
}
// ✅ Good: Early returns for cleaner code
pub fn good_nested_operation(&mut self, user_id: &str) -> app::Result<()> {
let user = self.users.get(user_id)?
.ok_or_else(|| app::err!("User not found"))?;
let profile = user.profile
.ok_or_else(|| app::err!("User profile not found"))?;
let settings = profile.settings
.ok_or_else(|| app::err!("User settings not found"))?;
settings.update()?;
Ok(())
}
Next Steps
- Learn about Calimero Collections for data storage
- See Rust Protocol SDK for basic macro usage