4 ms·
There's no meaningful order to shared dependencies or cyclic dependencies. You can pick one, but making it undefined is a lot more useful as the language implem
by duped 2y ago
There's no meaningful order to shared dependencies or cyclic dependencies. You can pick one, but making it undefined is a lot more useful as the language implementer.
As a language user, don't rely on order for your side effects. Actually, just don't have side effects on module load. You almost never need it. Lazily initialize your globals.
- Joker_vD 2y ago> There's no meaningful order to shared dependencies or cyclic dependencies. Textual order. At least it's visible. > Lazily initialize your globals. What does that even mean? Something like that: size_t _fwrite__impl(const void* buffer, size_t size, size_t count, FILE* stream) { if (!__crt0_initialized) { _crt0(); } if (!__crt1_initialized) { _crt1(); } if (!__xfloat_initialized) { _xfloat(); } if (__stdio_initialized) { _init_stdio(); } // Actual implementation that touches internal FILE-tables and maybe does float/double formatting. } ? But why? > Actually, just don't have side effects on module load. You almost never need it. See above. There is a surprising amount of invisibly initialized global state in e.g. C runtime library.
- duped 2y ago> Textual order. At least it's visible. Is "textual order" breadth-first, depth-first, reverse breadth-first, or reverse depth-first? Whichever you pick there will be a case where some module can't initialize because of assumptions it makes about how other modules are initialized. And like I said, it totally breaks down for cyclical dependencies - which are so common in practice, you must consider it. > ? But why? To paraphrase what Rust does, "no life before main." The point is to force expressions to be evaluated as they're used instead of as they're declared. There's an additional benefit that global resources that are not used are not initialized, which in the cases above has global side effects. The glibc runtime is not something to be held up as a model for something particularly well designed. You can get all the benefits of hidden global initialization via laziness without all the problems placed on the programmer to care about their import declaration order, or undefined cases like cyclical imports. One place where this stuff really sucks is when using dynamic linking and shared libraries have constructor functions that modify global state. GCC had to rollback changes to --ffast-math a couple years ago because loading two libraries compiled with different flags could result in undefined behavior when the MXCSR register depended on the order of library initialization.
- Joker_vD 2y agoTextual order is depth-first. When you encounter "import X", you switch to (recursively) import/load module X. I believe that's how Python works. As for the cyclical dependencies I'd argue they should be disallowed. Either their initialization order doesn't actually matter — in which case it doesn't matter :) — or there is a way to break things into smaller pieces and reorder them to function properly — in which case it's what should be done — or there is no valid ordering at all, in which case it's a genuine bug which has been made possible only because cyclical dependencies were allowed. > There's an additional benefit that global resources that are not used are not initialized This, arguably, can be considered a downside. Consider the security implications (and introduced mitigations) of e.g. writeable GOT/PLT. But it's a design decision with both of the choices valid, just with different trade-offs. > You can get all the benefits of hidden global initialization via laziness without all the problems placed on the programmer to care about their import declaration order I'd be interested to read about that. To me, this sounds mostly the problem of not accurately specifying your actual dependencies.