Skip to main content

init_static/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::collections::{HashMap, HashSet};
4use std::sync::Mutex;
5
6use anyhow::{Context, Result};
7use futures_util::StreamExt;
8use futures_util::stream::FuturesUnordered;
9
10use crate::__private::{INIT, InitFn};
11
12mod error;
13mod init_static;
14
15/// Macro to declare statically stored values with explicit initialization. Similar to
16/// [`lazy_static!`](lazy_static::lazy_static!), but initialization is not automatic.
17///
18/// Each static declared using this macro:
19///
20/// - Wraps the value type in [`InitStatic`](struct@InitStatic)
21/// - Generates an init function that sets the value
22/// - Registers the init function in a distributed slice
23///
24/// The values are initialized when [`init_static()`] is called.
25///
26/// # Example
27///
28/// ```
29/// use init_static::init_static;
30///
31/// init_static! {
32///     static VALUE: u32 = "42".parse()?;
33/// }
34///
35/// #[tokio::main]
36/// async fn main() {
37///     init_static().await.unwrap();
38///     println!("{}", *VALUE);
39/// }
40/// ```
41pub use init_static_macro::init_static;
42
43pub use crate::error::InitError;
44pub use crate::init_static::{InitStatic, Symbol};
45
46struct InitOptions {
47    debug: bool,
48}
49
50static INIT_OPTIONS: Mutex<Option<InitOptions>> = Mutex::new(Some(InitOptions { debug: false }));
51
52/// Enables or disables debug output during initialization.
53///
54/// When debug mode is enabled, the initialization process prints messages
55/// to stderr indicating:
56///
57/// - When each synchronous static is initialized
58/// - When each asynchronous static begins and completes initialization
59///
60/// This is useful for diagnosing initialization order issues or performance
61/// problems during startup.
62pub fn set_debug(debug: bool) {
63    INIT_OPTIONS
64        .lock()
65        .unwrap()
66        .as_mut()
67        .expect("INIT_OPTIONS can only be modified before `init_static` is called.")
68        .debug = debug;
69}
70
71/// Returns whether [`init_static()`] has already been called.
72///
73/// This function checks if the initialization process has been executed. It returns `true` if
74/// [`init_static()`] has been called (regardless of whether it succeeded or failed), and `false`
75/// otherwise.
76pub fn is_initialized() -> bool {
77    INIT_OPTIONS.lock().unwrap().is_none()
78}
79
80/// Runs initialization for all statics declared with [`init_static!`].
81///
82/// This function iterates over all init functions registered via the macro and executes them once.
83/// Call this early in your program (e.g., at the beginning of `main()`) before accessing any
84/// [`struct@InitStatic`] values.
85///
86/// # Examples
87///
88/// ```
89/// use init_static::init_static;
90///
91/// init_static! {
92///     static VALUE: u32 = "42".parse()?;
93/// }
94///
95/// #[tokio::main]
96/// async fn main() {
97///     init_static().await.unwrap();
98///     println!("{}", *VALUE);
99/// }
100/// ```
101pub async fn init_static() -> Result<()> {
102    let options = INIT_OPTIONS
103        .lock()
104        .unwrap()
105        .take()
106        .expect("`init_static` can only be called once.");
107
108    let mut symbol_map: HashMap<&'static Symbol, usize> = HashMap::new();
109    for (i, init) in INIT.iter().enumerate() {
110        if symbol_map.insert(init.symbol, i).is_some() {
111            return Err(InitError::Ambiguous { symbol: init.symbol }.into());
112        }
113    }
114
115    let mut adjacent = INIT
116        .iter()
117        .enumerate()
118        .map(|(i, init)| {
119            let deps = (init.deps)()
120                .into_iter()
121                .filter_map(|symbol| Some(*symbol_map.get(symbol?)?))
122                .collect::<HashSet<_>>();
123            (i, deps)
124        })
125        .collect::<Vec<_>>();
126
127    let mut join_set = FuturesUnordered::new();
128    while !adjacent.is_empty() || !join_set.is_empty() {
129        let layer = adjacent
130            .extract_if(.., |(_, deps)| deps.is_empty())
131            .map(|(i, _)| i)
132            .collect::<HashSet<_>>();
133        let mut has_sync = false;
134        for i in layer {
135            match &INIT[i].init {
136                InitFn::Sync(f) => {
137                    has_sync = true;
138                    if options.debug {
139                        eprintln!("init_static: sync {}", INIT[i].symbol);
140                    }
141                    f().with_context(|| format!("failed to initialize {}", INIT[i].symbol))?;
142                    for (_, deps) in &mut adjacent {
143                        deps.remove(&i);
144                    }
145                }
146                InitFn::Async(f) => join_set.push(async move {
147                    if options.debug {
148                        eprintln!("init_static: async begin {}", INIT[i].symbol);
149                    }
150                    let output = f()
151                        .await
152                        .map(|_| i)
153                        .with_context(|| format!("failed to initialize {}", INIT[i].symbol));
154                    if options.debug {
155                        eprintln!("init_static: async end {}", INIT[i].symbol);
156                    }
157                    output
158                }),
159            }
160        }
161        if has_sync {
162            continue;
163        }
164        if join_set.is_empty() {
165            return Err(InitError::Circular {
166                symbols: adjacent.iter().map(|(i, _)| INIT[*i].symbol).collect(),
167            }
168            .into());
169        }
170        let i = join_set.next().await.unwrap()?;
171        for (_, deps) in &mut adjacent {
172            deps.remove(&i);
173        }
174    }
175
176    Ok(())
177}
178
179#[doc(hidden)]
180pub mod __private {
181    use std::pin::Pin;
182
183    pub use {anyhow, linkme};
184
185    use crate::Symbol;
186    pub use crate::init_static::MaybeInitStatic;
187
188    pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T>>>;
189
190    pub enum InitFn {
191        Sync(fn() -> anyhow::Result<()>),
192        Async(fn() -> BoxFuture<anyhow::Result<()>>),
193    }
194
195    pub struct Init {
196        pub symbol: &'static Symbol,
197        pub init: InitFn,
198        pub deps: fn() -> Vec<Option<&'static Symbol>>,
199    }
200
201    #[linkme::distributed_slice]
202    pub static INIT: [Init];
203}