# Idea: limited life-before-main for statics runtime initialization

**URL:** <https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461>\
**Category:** language design\
**Created:** [October 19, 2021, 1:47am UTC](https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461 "2021-10-19T01:47:58Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![newpavlov](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/newpavlov/32/3290_2.png) [@newpavlov](https://internals.rust-lang.org/u/newpavlov)\
**Post date:** [October 19, 2021, 1:47am UTC](https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461/1 "2021-10-19T01:47:58Z")

</div>

Note: I know about this FAQ [entry](https://doc.rust-lang.org/1.4.0/complement-design-faq.html#there-is-no-life-before-or-after-main-(no-static-ctors/dtors)) in the old docs, but I think this problem still may be worth revisiting.

The most common advice when runstime initialization of `static`s is needed is to use crates like [`lazy_static`](https://docs.rs/lazy_static/). Such solutions work well enough in most of the cases, but in some rare cases they are quite sub-optimal. Under the hood they use synchronization primitives (usually atomics) to track if initialization has been performed. This means, that each use of such static has to include an initialization check branch. Not only it can be relatively expensive in hot loops, but more importantly it prevents compiler from applying some important optimizations (e.g. see this [post](https://users.rust-lang.org/t/64221), it's about atomics, but the same applies to lazy statics as well). Rust `std` itself suffers from the same issue in its `is_x86_feature_detected` macro.

Runtime static initialization may look roughly like this:

```rust
// the memory behind this static will be initially filled with zeros
#[init]
static FOO: u32 = || { .. };

// in the case of error main will return an error code by
// using the `Termination` trait
#[try_init]
pub static Bar: Vec<u32> = || {
    if flag {
        Ok(val)
    } else {
        Err(Error)
    }
};

```

IIUC the "static initialization order fiasco" can be mostly solved by forbidding initialization functions. In theory we may extended this feature to allow explicit specification of dependencies between statics initialized at runtime.

Instead of relying on linker, it may be worth for Rust to handle this initialization by itself, i.e. it could collect all such statics from dependencies and implicitly insert calls to initialization functions into beginning of the project's `main`.

Disadvantages of adding such feature are clear: additional compiler complexity and implicitly running code, which may cause unpleasant surprises (e.g. if abused, it may significantly increase program starting up time). Also in contrast with the "lazy" statics approach, static initialization code will be executed even if its value is not used anywhere.

But in some performance sensitive cases we need to know that a `static` was properly initialized without checking it by ourselves, which I think warrants addition of such feature. In some cases it may be worked around by caching its value on stack or by using initialization tokens, but it does not always work good enough (e.g. if `static` is too big or if we pass crate boundaries, which prevents API changes).

While my main concern is performance sensitive code, such statics may be useful for other [cases](https://internals.rust-lang.org/t/pre-rfc-life-before-main-lib-init/3855) as well.

What do you think about possibility of introducing life-before-main in the proposed form?

---

<div class="post-metadata">

**Author:** ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)\
**Post date:** [October 19, 2021, 2:47am UTC](https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461/2 "2021-10-19T02:47:14Z")

</div>

(no expressed opinion on the proposed feature)

(generally I prefer `OnceCell` to `lazy_static` nowadays)

The general solution to the problem for libs where using a global matters for performance is to either

- have the caller initialize a `Ctx` and pass references to that, or
- have the caller create a zero-sized `InitToken` that proves they've called a `init() -> InitToken`, which is truly zero-cost to pass as an argument, or
- if it really needs to be global data, have an `unsafe` requirement to call some initialization function

It's interesting that you're already using an `InitToken` to prove that the global cell has been initialized and won't be written again. It _should_ be possible to use `unsafe` to convince the optimizer to do a non-atomic read (and optimize as such). It's [clearly a bug that this currently doesn't happen](https://users.rust-lang.org/t/is-it-possible-to-tell-compiler-to-treat-an-atomic-value-as-immutable/64221/4?u=cad97) (and likely due to underutilization of `noalias`, which I believe is still enabled (finally)).

I'd even argue that a `Relaxed` ordering allows the desired optimization. I don't think life-before-main is required to get the result you're looking for in the linked urlo thread.

---

<div class="post-metadata">

**Author:** ![matklad](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matklad/32/12266_2.png) [@matklad](https://internals.rust-lang.org/u/matklad)\
**Post date:** [October 19, 2021, 7:02am UTC](https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461/3 "2021-10-19T07:02:39Z")

</div>

One idea I have along these lines is to put data into unmapped page, and let the init function map the page. That way you get a page fault when trying to access uninitialized data.

---

<div class="post-metadata">

**Author:** ![bjorn3](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/bjorn3/32/2736_2.png) [@bjorn3](https://internals.rust-lang.org/u/bjorn3)\
**Post date:** [October 19, 2021, 10:12am UTC](https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461/4 "2021-10-19T10:12:08Z")

</div>

> [@newpavlov](#):
>
> Instead of relying on linker, it may be worth for Rust to handle this initialization by itself, i.e. it could collect all such statics from dependencies and implicitly insert calls to initialization functions into beginning of the project's `main` .

This won't work if the main executable is not written in Rust you are loading a dynamically linked library at runtime using dlopen or you are avoiding the main shim because you for example are running on an embedded system. In those cases only using the same mechanism as C++ initializers (the linker support you talk about) will work, but that for example runs before libstd can store the commandline arguments in a static, thus causing a portability issues when trying to use `std::env::args()` from inside such an initializer.

---

<div class="post-metadata">

**Author:** ![bjorn3](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/bjorn3/32/2736_2.png) [@bjorn3](https://internals.rust-lang.org/u/bjorn3)\
**Post date:** [October 19, 2021, 10:13am UTC](https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461/5 "2021-10-19T10:13:19Z")

</div>

> [@matklad](#):
>
> One idea I have along these lines is to put data into unmapped page, and let the init function map the page. That way you get a page fault when trying to access uninitialized data.

If the main shim doesn't run the SIGSEGV handler won't be registered. Also not every platform has an MMU to allow for this trick.

---

<div class="post-metadata">

**Author:** ![Nemo157](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/nemo157/32/11585_2.png) [@Nemo157](https://internals.rust-lang.org/u/Nemo157)\
**Post date:** [October 19, 2021, 11:02am UTC](https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461/6 "2021-10-19T11:02:20Z")

</div>

Does `std` support systems without MMU? If this is part of the runtime it doesn't need to be supported on `core`-only targets.

---

<div class="post-metadata">

**Author:** ![ckaran](https://avatars.discourse-cdn.com/v4/letter/c/f475e1/32.png) [@ckaran](https://internals.rust-lang.org/u/ckaran)\
**Post date:** [October 19, 2021, 11:56am UTC](https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461/7 "2021-10-19T11:56:36Z")

</div>

> [@Nemo157](#):
>
> Does `std` support systems without MMU? If this is part of the runtime it doesn't need to be supported on `core` -only targets.

What about future platforms? Is it possible to create a supported platform that doesn't have an MMU and yet still supports `std`?

---

<div class="post-metadata">

**Author:** ![H2CO3](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/h2co3/32/2849_2.png) [@H2CO3](https://internals.rust-lang.org/u/H2CO3)\
**Post date:** [October 19, 2021, 12:48pm UTC](https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461/8 "2021-10-19T12:48:23Z")

</div>

> [@CAD97](#):
>
> (generally I prefer `OnceCell` to `lazy_static` nowadays)

+1

> [@CAD97](#):
>
> The general solution to the problem for libs where using a global matters for performance is to either
> 
> - have the caller initialize a `Ctx` and pass references to that

+1 to this as well. This almost universally results in better quality (_and_ slightly faster) code than a lazy global. Most of the time people claim they "need" a global, they could do just fine without one, and the corner cases are rare enough not to warrant breaking the important assumption (especially w.r.t. `unsafe`!) of no life-before-main.

---

<div class="post-metadata">

**Author:** ![bjorn3](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/bjorn3/32/2736_2.png) [@bjorn3](https://internals.rust-lang.org/u/bjorn3)\
**Post date:** [October 19, 2021, 3:02pm UTC](https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461/9 "2021-10-19T15:02:14Z")

</div>

> [@Nemo157](#):
>
> Does `std` support systems without MMU? If this is part of the runtime it doesn't need to be supported on `core` -only targets.

The Fortanix SGX target supports libstd and I would expect it to not allow catching SIGSEGV.

---

<div class="post-metadata">

**Author:** ![newpavlov](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/newpavlov/32/3290_2.png) [@newpavlov](https://internals.rust-lang.org/u/newpavlov)\
**Post date:** [October 19, 2021, 3:14pm UTC](https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461/10 "2021-10-19T15:14:12Z")

</div>

> [@bjorn3](#):
>
> This won't work if the main executable is not written in Rust you are loading a dynamically linked library

Compiler can apply different approaches while compiling Rust applications and shared libraries. It also may depend on target, e.g. in WASM we would have to use the [start function](https://github.com/WebAssembly/design/blob/main/Modules.md#module-start-function) capability.

> [@Nemo157](#):
>
> Does `std` support systems without MMU?

I think the WASM targets is one example of such systems.

> [@H2CO3](#):
>
> the corner cases are rare enough not to warrant breaking the important assumption (especially w.r.t. `unsafe` !) of no life-before-main.

Can you provide examples of such breakage? AFAIK it's already possible to use ctors today in Rust (e.g. see [`rust-ctor`](https://github.com/mmastrac/rust-ctor)), but it's platform-dependent and somewhat fragile solution.

---

<div class="post-metadata">

**Author:** ![bjorn3](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/bjorn3/32/2736_2.png) [@bjorn3](https://internals.rust-lang.org/u/bjorn3)\
**Post date:** [October 19, 2021, 8:23pm UTC](https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461/11 "2021-10-19T20:23:29Z")

</div>

> [@newpavlov](#):
>
> e.g. in WASM we would have to use the [start function](https://github.com/WebAssembly/design/blob/main/Modules.md#module-start-function) capability.

The start function runs before libc (eg wasi's libc) is initialized, which is wildly unsafe as basically any call into libc is UB at that point. Also using the start function won't work when eg linking a rustc generated wasm staticlib into a program using clang as main compiler.

> [@newpavlov](#):
>
> and shared libraries

For shared libraries the only option is C++ global initializers. As it isn't known in advance if a crate will be linked into an executable or shared library, this means that executables will have to use C++ global initializers too. Even if `--crate-type bin` is used, an additional `--crate-type cdylib` is allowed and will use the exact same object files. Only linling will be done twice.

> [@newpavlov](#):
>
> Can you provide examples of such breakage?

A dependency may wrap a C++ library which needs to have it's global initializers run before it is safe to call. Currently unsafe code doesn't have to worry about it as life-before-main isn't allowed, but if life-before-main becomes possible it will be possible to call into said C++ library before it is initialized if the global initializer implemented in rust happened to run before the one of the C++ library.

> [@newpavlov](#):
>
> AFAIK it's already possible to use ctors today in Rust (e.g. see [`rust-ctor`](https://github.com/mmastrac/rust-ctor)), but it's platform-dependent and somewhat fragile solution.

From the readme:

> Rust's philosophy is that nothing happens before or after main and this library explicitly subverts that. The code that runs in the `ctor` and `dtor` functions should be careful to limit itself to `libc` functions and code that does not rely on Rust's stdlib services.

I would go even further and say that rust-ctor is unsound as it doesn't require you to use the unsafe keyword.

---

<div class="post-metadata">

**Author:** ![newpavlov](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/newpavlov/32/3290_2.png) [@newpavlov](https://internals.rust-lang.org/u/newpavlov)\
**Post date:** [October 20, 2021, 8:57am UTC](https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461/12 "2021-10-20T08:57:18Z")

</div>

> [@bjorn3](#):
>
> The start function runs before libc (eg wasi's libc) is initialized, which is wildly unsafe as basically any call into libc is UB at that point.

Isn't `libc` initialized using the same start function? I think it should be possible for compiler to insert statics initialization after such environment setting code.

> [@bjorn3](#):
>
> A dependency may wrap a C++ library which needs to have it's global initializers run before it is safe to call.

Ah, yes, you are right. I guess it should be fine requiring `unsafe` for runtime static initialization, but I must say it feels somewhat backwards. From idealistic point of view it's wrapping C++ code reliant on global initializers should be unsound, not limiting Rust in using system capabilities, but I understand it's reasonable from practical point of view.

---

<div class="post-metadata">

**Author:** ![bjorn3](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/bjorn3/32/2736_2.png) [@bjorn3](https://internals.rust-lang.org/u/bjorn3)\
**Post date:** [October 20, 2021, 9:17am UTC](https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461/13 "2021-10-20T09:17:19Z")

</div>

> [@newpavlov](#):
>
> Isn't `libc` initialized using the same start function?

No, the `_start` export is called by wasi after the `start` function. This function is implemented by wasi-libc and first initializes libc, then calls all C++ static initializers and finally calls the user `main` function.

---

<div class="post-metadata">

**Author:** ![H2CO3](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/h2co3/32/2849_2.png) [@H2CO3](https://internals.rust-lang.org/u/H2CO3)\
**Post date:** [October 20, 2021, 1:38pm UTC](https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461/14 "2021-10-20T13:38:01Z")

</div>

> [@newpavlov](#):
>
> AFAIK it's already possible to use ctors today in Rust (e.g. see [`rust-ctor`](https://github.com/mmastrac/rust-ctor)), but it's platform-dependent and somewhat fragile solution.

And that's not an accident. It's like dynamic library unloading (which also came up a couple of days ago): it's surprisingly tricky to do it correctly, and naïve ideas/implementations are regularly full of soundness holes.

> [@newpavlov](#):
>
> Can you provide examples of such breakage?

Well, let me turn the question inside out: can you prove that it won't cause any soundness (or other) issues? I think the burden of proof should be on whoever proposes such a fundamental change.

Anyway, if life-before-main is disallowed, then one can assume two things:

1. Statements will be executed in the order deduced from the source code inside functions. For instance, many real-life libraries contain initialization routines which, unfortunately, rely on global or thread-local variables. If two such C libraries are used in a way that the Rust code calling them makes them depend on each other, suddenly initialization order becomes important. This is not in general true or solvable in the presence of life-before-main.
2. Any required functionality provided by std or core or libc (or other system resources) will be properly set up. I/O comes to mind as the primary example.

---

<div class="post-metadata">

**Author:** ![ckaran](https://avatars.discourse-cdn.com/v4/letter/c/f475e1/32.png) [@ckaran](https://internals.rust-lang.org/u/ckaran)\
**Post date:** [October 20, 2021, 2:02pm UTC](https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461/15 "2021-10-20T14:02:10Z")

</div>

> [@H2CO3](#):
>
> Anyway, if life-before-main is disallowed, then one can assume two things:
> 
> 1. Statements will be executed in the order deduced from the source code inside functions. For instance, many real-life libraries contain initialization routines which, unfortunately, rely on global or thread-local variables. If two such C libraries are used in a way that the Rust code calling them makes them depend on each other, suddenly initialization order becomes important. This is not in general true or solvable in the presence of life-before-main.
> 2. Any required functionality provided by std or core or libc (or other system resources) will be properly set up. I/O comes to mind as the primary example.

I want to amplify what @H2CO3 is saying here, because it's (IMHO) **important**. I spend maybe 50% of my time writing code, and then the other 50% of the time debugging it. This often involves point 1 that @H2CO3 made above, where I have to make logical inferences on what happened based on what code could or couldn't have been executed. If you introduce a lifetime before or after `main`, then you need a way of debugging it as well, or you end up with a lot of issues that are very, _very_ hard to resolve.

So, as @H2CO3 has asked, can you give an example where the methods that @CAD97 provided in [his earlier post](https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461/2) won't work? That is, something that truly requires a life before main, or a life before some init function is called?

---

<div class="post-metadata">

**Author:** ![jessa0](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jessa0/32/7802_2.png) [@jessa0](https://internals.rust-lang.org/u/jessa0)\
**Post date:** [October 20, 2021, 3:08pm UTC](https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461/16 "2021-10-20T15:08:34Z")

</div>

SGX in in fact capable of handling processor exceptions. The Intel SDK catches OS signals, detects that they originated from a processor exception inside an enclave, and reenters the enclave which can then tell what exception happened with what exact processor context and resume execution however it likes. Fortanix is certainly capable of doing the same.

---

<div class="post-metadata">

**Author:** ![ibraheemdev](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/ibraheemdev/32/13406_2.png) [@ibraheemdev](https://internals.rust-lang.org/u/ibraheemdev)\
**Post date:** [October 20, 2021, 9:53pm UTC](https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461/17 "2021-10-20T21:53:52Z")

</div>

Won't this become a non-issue with improvements to const-eval?

---

<div class="post-metadata">

**Author:** ![bjorn3](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/bjorn3/32/2736_2.png) [@bjorn3](https://internals.rust-lang.org/u/bjorn3)\
**Post date:** [October 20, 2021, 10:23pm UTC](https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461/18 "2021-10-20T22:23:41Z")

</div>

As far as I know at least the first version of SGX doesn't allow dynamically changing which parts of the address space are mapped and which are not as soon as the enclave has been sealed.

---

<div class="post-metadata">

**Author:** ![bjorn3](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/bjorn3/32/2736_2.png) [@bjorn3](https://internals.rust-lang.org/u/bjorn3)\
**Post date:** [October 20, 2021, 10:25pm UTC](https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461/19 "2021-10-20T22:25:23Z")

</div>

> [@ibraheemdev](#):
>
> Won't this become a non-issue with improvements to const-eval?

Const-eval can't handle interaction with the OS or any kind of non-determinism.

---

<div class="post-metadata">

**Author:** ![system](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/system/32/14092_2.png) [@system](https://internals.rust-lang.org/u/system)\
**Post date:** [January 18, 2022, 10:26pm UTC](https://internals.rust-lang.org/t/idea-limited-life-before-main-for-statics-runtime-initialization/15461/20 "2022-01-18T22:26:12Z")

</div>

Closed on January 18, 2022, 10:26pm UTC

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.
