Another option is to use OnceLock instead, have callers wait on it (currently on nightly) and set it on the main thread. While wait nominally blocks it won't actually block after initialization.
Or avoid global state if you can. E.g. actix has the app_data feature to handle state without making it global.
thread 'main' panicked at /home/user/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.38.0/src/runtime/scheduler/multi_thread/mod.rs:86:9:
Cannot start a runtime from within a runtime. This happens because a function (like `block_on`) attempted to block the current thread while the thread is being used to drive asynchronous tasks.
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
The actual initialization code runs when the future is polled, and that's not when the global is created, but when it's first accessed. You are probably accessing it for the first time once a runtime is already inplace.
Because access could need the tokio runtime, you will have to ensure the tokio runtime is set up and entered every time you use the global.
This whole setup seems silly. Does it have to be both lazy and global?
If it doesn't have to be lazy, you could create the value early in your program (eagerly in async fn main, not in any lazy callback), and synchronously assign it to the global.