canic_core/macros/
start.rs

1//! # Canic Lifecycle Macros
2//!
3//! These macros define **compile-time lifecycle entry points** (`init` and `post_upgrade`)
4//! for Canic canisters. Lifecycle hooks must exist at the crate root with fixed names,
5//! so they cannot be registered dynamically — macros are therefore used to generate the
6//! boilerplate pre- and post-initialization logic automatically.
7//!
8//! Each macro sets up configuration, memory, timers, and TLS before calling user-defined
9//! async setup functions (`canic_setup`, `canic_install`, `canic_upgrade`), and then
10//! exposes the standard Canic endpoint suites.
11//!
12//! ## When to use which
13//!
14//! - [`macro@canic::start`] — for **non-root** canisters (standard services, workers, etc.).
15//! - [`macro@canic::start_root`] — for the **root orchestrator**, which performs
16//!   additional initialization for global registries and root-only extensions.
17
18/// Configure lifecycle hooks for **non-root Canic canisters**.
19///
20/// This macro wires up the `init` and `post_upgrade` entry points required by the IC,
21/// performing pre-initialization steps (config, memory, TLS, environment) before invoking
22/// user async functions:
23///
24/// ```ignore
25/// async fn canic_setup() { /* shared setup */ }
26/// async fn canic_install(args: Option<Vec<u8>>) { /* called after init */ }
27/// async fn canic_upgrade() { /* called after post_upgrade */ }
28/// ```
29///
30/// These functions are spawned asynchronously after bootstrap completes.
31/// The macro also exposes the standard non-root Canic endpoint suites.
32///
33/// This macro must be used instead of a normal function because the IC runtime requires
34/// `init` and `post_upgrade` to be declared at the top level.
35
36#[macro_export]
37macro_rules! start {
38    ($canister_type:expr) => {
39        #[::canic::cdk::init]
40        fn init(payload: ::canic::core::ops::CanisterInitPayload, args: Option<Vec<u8>>) {
41            ::canic::core::__canic_load_config!();
42
43            // ops
44            ::canic::core::ops::runtime::nonroot_init($canister_type, payload);
45
46            // timers — async body, no spawn()
47            let install_args = args;
48            let _ = ::canic::core::ops::timer::TimerOps::set(
49                ::std::time::Duration::ZERO,
50                "startup:init",
51                async move {
52                    canic_setup().await;
53                    canic_install(install_args).await;
54                },
55            );
56        }
57
58        #[::canic::cdk::post_upgrade]
59        fn post_upgrade() {
60            ::canic::core::__canic_load_config!();
61
62            // ops
63            ::canic::core::ops::runtime::nonroot_post_upgrade($canister_type);
64
65            // timers — async body, no spawn()
66            let _ = ::canic::core::ops::timer::TimerOps::set(
67                ::std::time::Duration::ZERO,
68                "startup:upgrade",
69                async move {
70                    canic_setup().await;
71                    canic_upgrade().await;
72                },
73            );
74        }
75
76        ::canic::core::canic_endpoints!();
77        ::canic::core::canic_endpoints_nonroot!();
78    };
79}
80
81///
82/// Configure lifecycle hooks for the **root Canic orchestrator canister**.
83///
84/// This macro behaves like [`macro@canic::start`], but includes additional
85/// root-only initialization for:
86///
87/// - the global subnet registry
88/// - root-only memory extensions and cycle tracking
89/// - the root endpoint suite
90///
91/// It generates the `init` and `post_upgrade` hooks required by the IC, loads embedded
92/// configuration, imports the root `WASMS` bundle, and runs pre- and post-upgrade logic
93/// in [`ops::runtime_lifecycle`].
94///
95/// Use this for the root orchestrator canister only. Other canisters should use
96/// [`macro@canic::start`].
97
98#[macro_export]
99macro_rules! start_root {
100    () => {
101        #[::canic::cdk::init]
102        fn init(identity: ::canic::core::model::memory::topology::SubnetIdentity) {
103            ::canic::core::__canic_load_config!();
104
105            // ops
106            ::canic::core::ops::runtime::root_init(identity);
107
108            // import wasms
109            ::canic::core::ops::wasm::WasmOps::import_static(WASMS);
110
111            // timers
112            let _ = ::canic::core::ops::timer::TimerOps::set(
113                std::time::Duration::ZERO,
114                "startup:root",
115                async move {
116                    ::canic::core::ops::root::root_set_subnet_id().await;
117
118                    // attempt to create canisters
119                    if let Err(err) = ::canic::core::ops::root::root_create_canisters().await {
120                        $crate::log!(
121                            $crate::log::Topic::Init,
122                            Error,
123                            "root_create_canisters failed: {err}"
124                        );
125                        return;
126                    }
127
128                    canic_setup().await;
129                    canic_install().await;
130                },
131            );
132        }
133
134        #[::canic::cdk::post_upgrade]
135        fn post_upgrade() {
136            ::canic::core::__canic_load_config!();
137            ::canic::core::ops::wasm::WasmOps::import_static(WASMS);
138
139            // ops
140            ::canic::core::ops::runtime::root_post_upgrade();
141
142            // timers
143            let _ = ::canic::core::ops::timer::TimerOps::set(
144                ::std::time::Duration::ZERO,
145                "startup:root-upgrade",
146                async move {
147                    canic_setup().await;
148                    canic_upgrade().await;
149                },
150            );
151        }
152
153        ::canic::core::canic_endpoints!();
154        ::canic::core::canic_endpoints_root!();
155    };
156}
157
158//
159// Private helpers
160//
161
162///
163/// Load the embedded configuration during init and upgrade hooks.
164///
165/// This macro exists solely to embed and load the TOML configuration file
166/// at compile time (`CANIC_CONFIG_PATH`). It is used internally by
167/// [`macro@canic::start`] and [`macro@canic::start_root`].
168
169#[doc(hidden)]
170#[macro_export]
171macro_rules! __canic_load_config {
172    () => {
173        #[cfg(canic)]
174        {
175            let config_str = include_str!(env!("CANIC_CONFIG_PATH"));
176            $crate::config::Config::init_from_toml(config_str).unwrap();
177        }
178    };
179}