bity_ic_canister_state_macros/lib.rs
1//! Module for managing canister state in Internet Computer canisters.
2//!
3//! This module provides a macro for creating thread-safe state management in canisters,
4//! with functions for initialization, reading, and modifying the state.
5//!
6//! # Example
7//! ```
8//! use bity_dfinity_library::canister_state_macros::canister_state;
9//!
10//! struct MyState {
11//! counter: u64,
12//! }
13//!
14//! canister_state!(MyState);
15//!
16//! fn init() {
17//! init_state(MyState { counter: 0 });
18//! }
19//!
20//! fn increment() {
21//! mutate_state(|state| state.counter += 1);
22//! }
23//! ```
24
25/// A macro that generates thread-safe state management functions for a canister.
26///
27/// This macro creates a set of functions for managing the canister's state in a thread-safe manner.
28/// It provides functions for initialization, reading, and modifying the state.
29///
30/// # Arguments
31/// * `$type` - The type of the state to manage
32///
33/// # Generated Functions
34/// * `init_state(state: $type)` - Initializes the state (panics if already initialized)
35/// * `replace_state(state: $type) -> $type` - Replaces the current state and returns the old one
36/// * `take_state() -> $type` - Takes ownership of the current state
37/// * `read_state<F, R>(f: F) -> R` - Reads the state using a closure
38/// * `mutate_state<F, R>(f: F) -> R` - Mutates the state using a closure
39/// * `can_borrow_state() -> bool` - Checks if the state can be borrowed
40///
41/// # Example
42/// ```
43/// use bity_dfinity_library::canister_state_macros::canister_state;
44///
45/// struct AppState {
46/// users: Vec<String>,
47/// }
48///
49/// canister_state!(AppState);
50///
51/// fn add_user(name: String) {
52/// mutate_state(|state| state.users.push(name));
53/// }
54///
55/// fn get_user_count() -> usize {
56/// read_state(|state| state.users.len())
57/// }
58/// ```
59#[macro_export]
60macro_rules! canister_state {
61 ($type:ty) => {
62 thread_local! {
63 static __STATE: std::cell::RefCell<Option<$type>> = std::cell::RefCell::default();
64 }
65
66 const __STATE_ALREADY_INITIALIZED: &str = "State has already been initialized";
67 const __STATE_NOT_INITIALIZED: &str = "State has not been initialized";
68
69 /// Initializes the canister state.
70 ///
71 /// # Arguments
72 /// * `state` - The initial state value
73 ///
74 /// # Panics
75 /// Panics if the state has already been initialized
76 pub fn init_state(state: $type) {
77 __STATE.with_borrow_mut(|s| {
78 if s.is_some() {
79 panic!("{}", __STATE_ALREADY_INITIALIZED);
80 } else {
81 *s = Some(state);
82 }
83 });
84 }
85
86 /// Replaces the current state with a new one.
87 ///
88 /// # Arguments
89 /// * `state` - The new state value
90 ///
91 /// # Returns
92 /// The previous state value
93 ///
94 /// # Panics
95 /// Panics if the state has not been initialized
96 pub fn replace_state(state: $type) -> $type {
97 __STATE.replace(Some(state)).expect(__STATE_NOT_INITIALIZED)
98 }
99
100 /// Takes ownership of the current state.
101 ///
102 /// # Returns
103 /// The current state value
104 ///
105 /// # Panics
106 /// Panics if the state has not been initialized
107 pub fn take_state() -> $type {
108 __STATE.take().expect(__STATE_NOT_INITIALIZED)
109 }
110
111 /// Reads the state using a closure.
112 ///
113 /// # Arguments
114 /// * `f` - A closure that takes a reference to the state and returns a value
115 ///
116 /// # Returns
117 /// The result of the closure
118 ///
119 /// # Panics
120 /// Panics if the state has not been initialized
121 pub fn read_state<F, R>(f: F) -> R
122 where
123 F: FnOnce(&$type) -> R,
124 {
125 __STATE.with_borrow(|s| f(s.as_ref().expect(__STATE_NOT_INITIALIZED)))
126 }
127
128 /// Mutates the state using a closure.
129 ///
130 /// # Arguments
131 /// * `f` - A closure that takes a mutable reference to the state and returns a value
132 ///
133 /// # Returns
134 /// The result of the closure
135 ///
136 /// # Panics
137 /// Panics if the state has not been initialized
138 pub fn mutate_state<F, R>(f: F) -> R
139 where
140 F: FnOnce(&mut $type) -> R,
141 {
142 __STATE.with_borrow_mut(|s| f(s.as_mut().expect(__STATE_NOT_INITIALIZED)))
143 }
144
145 /// Checks if the state can be borrowed.
146 ///
147 /// # Returns
148 /// `true` if the state can be borrowed, `false` otherwise
149 pub fn can_borrow_state() -> bool {
150 __STATE.with(|s| s.try_borrow().is_ok())
151 }
152 };
153}