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}