init_static/
lib.rs

1#[doc = include_str!("../README.md")]
2use std::collections::{HashMap, HashSet};
3use std::error::Error;
4use std::fmt::{Debug, Display};
5use std::sync::Mutex;
6
7use futures_util::StreamExt;
8use futures_util::stream::FuturesUnordered;
9pub use init_static_macro::init_static;
10
11use crate::__private::INIT;
12pub use crate::init_static::{InitStatic, Symbol};
13
14mod init_static;
15
16struct InitOptionsInner {
17    debug: bool,
18}
19
20pub struct InitOptions {
21    inner: Mutex<Option<InitOptionsInner>>,
22}
23
24impl InitOptions {
25    pub fn debug(&self, debug: bool) {
26        self.inner
27            .lock()
28            .unwrap()
29            .as_mut()
30            .expect("INIT_OPTIONS can only be modified before `init_static` is called.")
31            .debug = debug;
32    }
33}
34
35pub static INIT_OPTIONS: InitOptions = InitOptions {
36    inner: Mutex::new(Some(InitOptionsInner { debug: false })),
37};
38
39/// Runs initialization for all statics declared with [`init_static!`].
40///
41/// This function iterates over all init functions registered via the macro and executes them once.
42/// Call this early in your program (e.g., at the beginning of `main()`) before accessing any
43/// [`InitStatic`] values.
44///
45/// # Examples
46///
47/// ```
48/// use init_static::init_static;
49/// use std::error::Error;
50///
51/// init_static! {
52///     static VALUE: u32 = "42".parse()?;
53/// }
54///
55/// #[tokio::main]
56/// async fn main() -> Result<(), Box<dyn Error>> {
57///     init_static().await?;
58///     println!("{}", *VALUE);
59///     Ok(())
60/// }
61/// ```
62pub async fn init_static() -> Result<(), InitError> {
63    let options = INIT_OPTIONS
64        .inner
65        .lock()
66        .unwrap()
67        .take()
68        .expect("`init_static` can only be called once.");
69
70    let mut symbol_map: HashMap<&'static Symbol, usize> = HashMap::new();
71    for (i, init) in INIT.iter().enumerate() {
72        if symbol_map.insert(init.symbol, i).is_some() {
73            return Err(InitError::Ambiguous { symbol: init.symbol });
74        }
75    }
76
77    let mut adjacent = INIT
78        .iter()
79        .enumerate()
80        .map(|(i, init)| {
81            let deps = (init.deps)()
82                .into_iter()
83                .filter_map(|symbol| Some(*symbol_map.get(symbol?)?))
84                .collect::<HashSet<_>>();
85            (i, deps)
86        })
87        .collect::<Vec<_>>();
88
89    let mut join_set = FuturesUnordered::new();
90    while !adjacent.is_empty() || !join_set.is_empty() {
91        let layer = adjacent
92            .extract_if(.., |(_, deps)| deps.is_empty())
93            .map(|(i, _)| i)
94            .collect::<HashSet<_>>();
95        join_set.extend(layer.into_iter().map(|i| async move {
96            if options.debug {
97                eprintln!("init_static: begin {}", INIT[i].symbol);
98            }
99            let output = (INIT[i].init)().await;
100            if options.debug {
101                eprintln!("init_static: end {}", INIT[i].symbol);
102            }
103            output.map(|_| i)
104        }));
105        if join_set.is_empty() {
106            return Err(InitError::Circular {
107                symbols: adjacent.iter().map(|(i, _)| INIT[*i].symbol).collect(),
108            });
109        }
110        match join_set.next().await.unwrap() {
111            Ok(i) => {
112                for (_, deps) in &mut adjacent {
113                    deps.remove(&i);
114                }
115            }
116            Err(e) => return Err(InitError::Execution(e)),
117        }
118    }
119
120    Ok(())
121}
122
123#[derive(Debug)]
124pub enum InitError {
125    Ambiguous { symbol: &'static Symbol },
126    Circular { symbols: Vec<&'static Symbol> },
127    Execution(anyhow::Error),
128}
129
130impl Display for InitError {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        match self {
133            Self::Ambiguous { symbol } => {
134                write!(f, "Symbol {symbol} is defined multiple times.")
135            }
136            Self::Circular { symbols } => {
137                writeln!(f, "Circular dependency detected among:")?;
138                for symbol in symbols {
139                    writeln!(f, "    {symbol}")?;
140                }
141                Ok(())
142            }
143            Self::Execution(e) => Display::fmt(e, f),
144        }
145    }
146}
147
148impl Error for InitError {
149    fn source(&self) -> Option<&(dyn Error + 'static)> {
150        match self {
151            Self::Execution(e) => Some(&**e),
152            _ => None,
153        }
154    }
155}
156
157#[doc(hidden)]
158pub mod __private {
159    use std::pin::Pin;
160
161    pub use {anyhow, linkme};
162
163    use crate::Symbol;
164    pub use crate::init_static::MaybeInitStatic;
165
166    pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T>>>;
167
168    pub struct Init {
169        pub symbol: &'static Symbol,
170        pub init: fn() -> BoxFuture<anyhow::Result<()>>,
171        pub deps: fn() -> Vec<Option<&'static Symbol>>,
172    }
173
174    #[linkme::distributed_slice]
175    pub static INIT: [Init];
176}