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::lifecycle::nonroot_init($canister_type, payload);
45
46 // timers — async body, no spawn()
47 let _ =
48 ::canic::cdk::timers::set_timer(::std::time::Duration::from_secs(0), async move {
49 canic_setup().await;
50 canic_install(args).await;
51 });
52 }
53
54 #[::canic::cdk::post_upgrade]
55 fn post_upgrade() {
56 ::canic::core::__canic_load_config!();
57
58 // ops
59 ::canic::core::ops::lifecycle::nonroot_post_upgrade($canister_type);
60
61 // timers — async body, no spawn()
62 let _ =
63 ::canic::cdk::timers::set_timer(::std::time::Duration::from_secs(0), async move {
64 canic_setup().await;
65 canic_upgrade().await;
66 });
67 }
68
69 ::canic::core::canic_endpoints!();
70 ::canic::core::canic_endpoints_nonroot!();
71 };
72}
73
74///
75/// Configure lifecycle hooks for the **root Canic orchestrator canister**.
76///
77/// This macro behaves like [`macro@canic::start`], but includes additional
78/// root-only initialization for:
79///
80/// - the global subnet registry
81/// - root-only memory extensions and cycle tracking
82/// - the root endpoint suite
83///
84/// It generates the `init` and `post_upgrade` hooks required by the IC, loads embedded
85/// configuration, imports the root `WASMS` bundle, and runs pre- and post-upgrade logic
86/// in [`ops::lifecycle`].
87///
88/// Use this for the root orchestrator canister only. Other canisters should use
89/// [`macro@canic::start`].
90
91#[macro_export]
92macro_rules! start_root {
93 () => {
94 #[::canic::cdk::init]
95 fn init(identity: ::canic::core::model::memory::topology::SubnetIdentity) {
96 ::canic::core::__canic_load_config!();
97
98 // ops
99 ::canic::core::ops::lifecycle::root_init(identity);
100
101 // import wasms
102 ::canic::core::ops::wasm::WasmOps::import_static(WASMS);
103
104 // timers
105 let _ =
106 ::canic::cdk::timers::set_timer(std::time::Duration::from_secs(0), async move {
107 ::canic::core::ops::root::root_set_subnet_id().await;
108
109 // attempt to create canisters
110 if let Err(err) = ::canic::core::ops::root::root_create_canisters().await {
111 $crate::log!(
112 $crate::log::Topic::Init,
113 Error,
114 "root_create_canisters failed: {err}"
115 );
116 return;
117 }
118
119 canic_setup().await;
120 canic_install().await;
121 });
122 }
123
124 #[::canic::cdk::post_upgrade]
125 fn post_upgrade() {
126 ::canic::core::__canic_load_config!();
127 ::canic::core::ops::wasm::WasmOps::import_static(WASMS);
128
129 // ops
130 ::canic::core::ops::lifecycle::root_post_upgrade();
131
132 // timers
133 let _ =
134 ::canic::cdk::timers::set_timer(::std::time::Duration::from_secs(0), async move {
135 canic_setup().await;
136 canic_upgrade().await;
137 });
138 }
139
140 ::canic::core::canic_endpoints!();
141 ::canic::core::canic_endpoints_root!();
142 };
143}
144
145//
146// Private helpers
147//
148
149///
150/// Load the embedded configuration during init and upgrade hooks.
151///
152/// This macro exists solely to embed and load the TOML configuration file
153/// at compile time (`CANIC_CONFIG_PATH`). It is used internally by
154/// [`macro@canic::start`] and [`macro@canic::start_root`].
155
156#[doc(hidden)]
157#[macro_export]
158macro_rules! __canic_load_config {
159 () => {
160 #[cfg(canic)]
161 {
162 let config_str = include_str!(env!("CANIC_CONFIG_PATH"));
163 $crate::config::Config::init_from_toml(config_str).unwrap();
164 }
165 };
166}