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}