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}