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::storage::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::ic::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::ic::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::ops::storage::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::ic::timer::TimerOps::set(
113 std::time::Duration::ZERO,
114 "startup:root",
115 async move {
116 ::canic::core::ops::bootstrap::root::root_set_subnet_id().await;
117
118 // attempt to create canisters
119 if let Err(err) =
120 ::canic::core::ops::bootstrap::root::root_create_canisters().await
121 {
122 $crate::log!(
123 $crate::log::Topic::Init,
124 Error,
125 "root_create_canisters failed: {err}"
126 );
127 return;
128 }
129
130 canic_setup().await;
131 canic_install().await;
132 },
133 );
134 }
135
136 #[::canic::cdk::post_upgrade]
137 fn post_upgrade() {
138 ::canic::core::__canic_load_config!();
139 ::canic::core::ops::wasm::WasmOps::import_static(WASMS);
140
141 // ops
142 ::canic::core::ops::runtime::root_post_upgrade();
143
144 // timers
145 let _ = ::canic::core::ops::ic::timer::TimerOps::set(
146 ::std::time::Duration::ZERO,
147 "startup:root-upgrade",
148 async move {
149 canic_setup().await;
150 canic_upgrade().await;
151 },
152 );
153 }
154
155 ::canic::core::canic_endpoints!();
156 ::canic::core::canic_endpoints_root!();
157 };
158}
159
160//
161// Private helpers
162//
163
164///
165/// Load the embedded configuration during init and upgrade hooks.
166///
167/// This macro exists solely to embed and load the TOML configuration file
168/// at compile time (`CANIC_CONFIG_PATH`). It is used internally by
169/// [`macro@canic::start`] and [`macro@canic::start_root`].
170
171#[doc(hidden)]
172#[macro_export]
173macro_rules! __canic_load_config {
174 () => {{
175 let config_str = include_str!(env!("CANIC_CONFIG_PATH"));
176 if let Err(err) = $crate::config::Config::init_from_toml(config_str) {
177 $crate::cdk::println!(
178 "[canic] FATAL: config init failed (CANIC_CONFIG_PATH={}): {err}",
179 env!("CANIC_CONFIG_PATH")
180 );
181 let msg = format!(
182 "canic init failed: config init failed (CANIC_CONFIG_PATH={}): {err}",
183 env!("CANIC_CONFIG_PATH")
184 );
185 $crate::cdk::api::trap(&msg);
186 }
187 }};
188}