<p>Mainly, I want to know what existing languages/implementations that use this sort of multi-file module structure do.</p>
<p><strong>In my language, modules can consist of multiple files</strong>. (For now I don't plan on having the compiler operate on anything other than complete modules.) A module's effective source text is roughly* a concatenation of the text of the files. For the sake of this post, let's just say that the “top level” of a module contains declarations that may or may not be exported from the module. A module declaration is necessary for the compiler to associate a name with the module, which is used when referring to exported things. I haven't decided whether I want the files to be specified explicitly (in the module declaration) or if they are implicit (based on directory structure). The latter choice is more conventional but less portable (can't do it with the standard C library). <strong>Specifying files would solve the problem that this question is about</strong>, although I'm not aware of any languages that do it (except for Common Lisp if you consider ASDF to be its module system).</p>
<p><strong>When a module has more than one file, the compiler needs to read all the files.</strong> In general in my language, the order of declarations doesn't matter, but there are edge cases when it does. <strong>In what order should the files be read/processed?</strong> Here are the choices I've thought of.</p>
<ul>
<li>Leave it unspecified; portable code shouldn't depend on the order. Fine in concept but there are some issues. First, it <em>is</em> possible for declarations to have different semantics depending on the order in which they appear, and the compiler can't always tell when this is the case. Thus the user is left to enforce the requirement. The compiler still needs to decide on an order; directory order is probably the simplest.</li>
<li>Directory order, assuming the files are specified implicitly. It isn't portable and I believe means that a program's compilation might break if you move it to a different file system. It is also not easily controllable by users.</li>
<li>Specification order, if files are explicitly listed. This gives the user control over the order and means that source files can be located anywhere; it's also generally portable between most platforms, as long as the files aren't in system-specific locations.</li>
</ul>
<p>I like the idea of specifying files, but again I believe it's uncommon. Perhaps it should be <em>possible</em> to specify the files if you need the control and the compiler defaults to directory order.</p>
<p>I'm glossing over details like where the module declaration goes. Also note that I'm not talking about the order of compilation of other modules.</p>
<p>If I understand correctly, Rust's module system is similar to what I want for my language. I couldn't find anything about the Rust compiler's file processing order.</p>
<hr />
<p>* A related concern is whether the files are simplistically concatenated as with the C preprocessor's <code>#include</code> or parsed as complete sequences of declarations and then combined somehow. If I go with dumb concatenation, then constructing examples where order matters is much easier. However, I might require an “in module” declaration in each constituent file, which would effectively require files to be syntactically complete. I'm leaning towards parsing files separately, since it seems the more “modern” choice.</p>

<compilers><implementation><modules>
How do languages where multiple files make up a module handle combining them into one translation/compilation unit?
CC BY-SA 4.0
2024-06-21T03:59:00.870Z
It *seems* like what you are describing is precisely the Go package system. What are you looking for an answer to provide here - existing analogous models, design research, general discussion? That is, what makes for a correct answer to this question?
Michael Homer
CC BY-SA 4.0
2024-06-21T04:07:29.910Z
@MichaelHomer Mainly I want to know what existing languages/implementations do. I don't know Go, but from cursory research it seems you can split a package between files by putting the package declaration in every file. Is the compiler able to compile each file separately? That isn't really an option for my language. (Not that that invalidates the comparison.)
texdr.aft
CC BY-SA 4.0
2024-06-21T04:30:11.343Z
Ok, I've made an edit to try to make that clear (so that it doesn't sound like a bikeshed, opinionated question, but looking for actual existing manifestations of this pattern). Please adjust if I've missed your meaning somewhere.
Michael Homer
CC BY-SA 4.0
2024-06-21T05:20:56.783Z
I'm not sure if I understand: is file processing order semantically meaningful in your language? Because if it is, you have to let the user choose and define that order. And if it is not, it is irrelevant whether you use directory order or not.
Eldritch Conundrum
CC BY-SA 4.0
2024-06-21T05:31:01.573Z
@EldritchConundrum It is meaningful but only in the sense that the order of declarations is meaningful and files in a module contain declarations.
texdr.aft
CC BY-SA 4.0
2024-06-21T13:34:27.407Z
@EldritchConundrum Not necessarily, static initialization in Java depends on order but cannot be specified either.
feldentm
CC BY-SA 4.0
2024-06-21T13:35:41.197Z
@texdr.aft you should think about removing all properties of your language that make the order of declarations meaningful. It improves usability, simplifies refactorings and makes tool support easier to implement.
feldentm
CC BY-SA 4.0
2024-06-21T17:20:37.523Z
Do you have examples where the order of declarations matters? Either for your language specifically or in another language. Maybe I'm missing something obvious but it seems trivial to disallow most/all of these (at least in most languages I'm familiar with).
CoffeeTableEspresso
CC BY-SA 4.0
2024-06-21T17:44:12.857Z
@CoffeeTableEspresso Initialization code might have side effects. Also outer declarations can be shadowed. (I know shadowing is controversial but I want it.)
texdr.aft
CC BY-SA 4.0
2024-06-21T20:24:41.943Z
It's important to distinguish the concepts of "edge cases where order of declaration may affect program behavior" and "edge cases where the order of declaration *matters*".  There are many scenarios where several observably-different program behaviors would be equally acceptable, and the fact that declaration order might affect which behavior is chosen would not imply that one nail down rules for how it does so as opposed to recognizing that certain choices are made in "unspecified" fashion.
supercat
CC BY-SA 4.0
2024-06-21T20:35:53.873Z
@texdr.aft I personally don't see a use for shadowing declarations at the top level like that. For the initialization example, when is initialization done? At compile time? At program start up?
CoffeeTableEspresso
<p>Frame challenge:</p>
<blockquote>
<p>In general in my language, the order of declarations doesn't matter, but there are edge cases when it does.</p>
</blockquote>
<p>That's not great. If &quot;independent&quot; units aren't independent, then it makes it hard for <em>humans</em> to make sense of things. If your compiler can't figure out what the semantics of loading the files in different orders should be, how would a programmer be able to do it? If the behavior is different depending on which order they're combined, how does the human reading the code know which behavior is the intended one?</p>
<p>This is a particular challenge if it's just &quot;edge cases&quot; where it matters. If 99+% of the time the combination is order independent, but there's strange 1% cases where it does, then a normal (non-expert) programmer will likely only be aware of the 99% behavior and wouldn't necessarily be on the lookout for the 1% edge case. They'll they'll possibly run into bugs where the compiler chose the one order, but they're implicitly expecting the other behavior.</p>
<p>Since you have control of the semantics of your programming language, I would highly recommend changing things such that you remove these edge cases. It's likely worth it to go through contortions (on the compiler side) to make that happen. You don't specify which edge cases you're running into, but approaches like having a two-phase parse (e.g. one phase just to pull out what definitions are available and then the second phase to actually use those definitions) might work for your issues. -- Again, think about how a programmer would determine what the intended behavior is, and try to make the compiler &quot;intelligent&quot; enough to pick that up.</p>
<p>If the order dependence is something more along the lines of multiple incompatible definitions for the same name (or something similar where there's contradictory programmer intent), then I'd highly recommend not trying to automatically resolve it. If the programmer isn't being clear, throw up a compiler error. Implicit rules where certain parts of the program are ignored or overwritten because of arbitrary order dependency issues are horrible to debug. (Why isn't the function returning the value it's supposed to? Because the compiler isn't calling that function, it's calling some other function you're not even aware of. Why? Because someone else renamed an unrelated file from <code>user_account.xyz</code> to <code>customer_account.xyz</code>, and since 'c' comes before most of the rest of the alphabet, the compiler is prioritizing those functions.) -- The compiler often doesn't need to determine an order, the compiler can simply kick the issue back to the programmer to resolve properly.</p>
<p>I'm guessing the issues you're having with order dependence can be resolved either by putting a bit more &quot;intelligence&quot; into your compiler, or by simply refusing to compile an order-dependent program. If, for some reason you can't resolve the issues in those cases (e.g. because there's no way for the programmer to rewrite the program to avoid them, or the compiler fundamentally can't be adjusted to support intelligent determination or error detection), then it's probably best to make the order dependence explicit in all cases, not just the edge cases. That way you don't have programmers who have standard work patterns for the 99+% cases, but then run into hairy debugging issues when the edge cases eventually pop up.</p>

CC BY-SA 4.0
2024-06-21T16:56:05.193Z
2024-06-21T16:56:05.193Z
R.M.
CC BY-SA 4.0
2024-06-21T18:04:01.580Z
Hmm... good points here. I'll probably have a way for the programmer to say "hey I'm gonna shadow this binding" and otherwise the compiler will tell them "hey just so you know you're shadowing this binding". But how might initialization be handled in an order independent way? In `x: int := f(); y: int := g()`, if `f` and `g` can have side effects then it may make a difference to have `x` after `y`. Tracking dependencies isn't always possible.
texdr.aft
CC BY-SA 4.0
2024-06-21T18:30:33.647Z
@texdr.aft Again, if side effects matter, how does a human know what should happen? -- One is to require initializer functions like f() and g() be without side effects. This could be explicitly checked (by having special "pure" functions either in the language proper or in the compiler internals). Or you could require non-trivial initialization to happen explicitly in standard control flow. (Rather than implicitly "before the program starts".) Or you could take the C/C++ route and say "order-dependent static initialization is undefined behavior: don't count on _either_ order."
R.M.
CC BY-SA 4.0
2024-06-21T22:05:49.787Z
@R.M. small correction, it's only a problem  in C++,  not C. C doesn't allow you to initialize static variables with just anything, just with constants.
CoffeeTableEspresso
CC BY-SA 4.0
2024-06-21T22:57:02.743Z
Since the question is seeking **instances of existing systems that do this**, this is not an answer to it (if it were an answer, the question would likely be off-topic). It could be edited to give supporting evidence of such systems being tried without success, for example, and make this case.
Michael Homer
CC BY-SA 4.0
2024-06-26T03:40:42.597Z
@MichaelHomer I think C++'s "order-dependent static initialization" is a pretty good example of an existing system that does this, and it causes endless problems
CoffeeTableEspresso
CC BY-SA 4.0
2024-06-26T04:03:15.157Z
@CoffeeTableEspresso I don't disagree that this is probably a bad idea when it's avoidable, but the answer as currently put doesn't back itself up - it's reckons on a background point, but the question seeks experience from other languages that have had this. We don't allow "rate my language design" questions, and that's what this would be an answer to currently (admittedly, there is a lot of excess detail in the question body). The answer could be reframed in light of negative examples like that and make this case, using C++/other examples to address the question: they do it *poorly* because…
Michael Homer
<p>I think that what you're describing is almost exactly the <a href="https://go.dev/ref/spec#Package_initialization" rel="nofollow noreferrer">Go package system</a>:</p>
<ul>
<li>Declarations from multiple files are combined into a single unit.</li>
<li>Each file has a <code>package</code> header specifying which package it belongs to.</li>
<li>There is no defined ordering between files, and no way to specify one in the code.</li>
<li>Declarations from any file in the package are visible throughout all files.</li>
<li>Ordering is visible in two ways:
<ul>
<li>Declared variables are initialised in a topological order across all files, evaluating only after all manifest dependencies are initialised.</li>
<li>There may be multiple <code>init</code> functions, which are all executed after the variables are initialised.</li>
</ul>
</li>
<li>The compiler identifies all available Go source files within the source root, and collates them by the package declaration.
<ul>
<li>Individual files have no meaning and cannot be processed apart from their module.</li>
</ul>
</li>
<li>Directory structuring is conventional, but the package declaration is definitive.</li>
</ul>
<p>In practice, the ordering between files will most often be lexically by filename, but as far as I know that is not <em>required</em>, and it can be overridden in the compiler invocation. The <em>other</em> steps make this order largely irrelevant, and are really how it is able to get away with this multiple-file approach. You can observe the file ordering with side-effecting initialisation code, but it's hard to have order-dependent <em>problems</em>. However, it isn't completely impossible to craft a scenario that goes wrong.</p>
<p>Subject to topological constraints, top-to-bottom initialisation is favoured. This means that if you have several variables, spread between files, some of which depend on some variable from another file, you'd expect the topmost variable with no dependencies in the lexicographically earliest file to evaluate first, then all the other zero-dependency variables, then the topmost variable in the earliest file that depended only on variables that have already been initialised, and so on. This topological ordering eliminates almost all of the potential ordering complications that could arise from any ordering of the module files.</p>
<p><code>init()</code> functions are executed after all variables initialisations have run, in the same source order as zero-dependency variables. I <em>believe</em> that at one point their order was explicitly unspecified, so this has been a change (although I think it may codify what the reference compiler always did). They can rely on all declared variables with initialisers throughout the entire package having been evaluated already. It is <em>possible</em> to create bad orderings this way, but it's not going to happen in the typical cases.</p>
<p>Go's package model has had some controversy over time, both to do with potential variations in initialisation order and its insistence on tree-structured (acyclic) imports. There's also been a later <em>module</em> system overlaid on it at a higher level; a module is a collection of packages.</p>
<p>Notwithstanding that, this is a practical realisation of pretty much exactly the approach you're describing and it's been around for a while, so there is a lot of practical experience with it and plenty of code written using it. You can find the complaints people have about it and see whether they are impactful for you. Overall, it seems to work just fine for many or most cases.</p>

CC BY-SA 4.0
2024-06-21T05:17:47.383Z
2024-06-21T05:17:47.383Z
Michael Homer
CC BY-SA 4.0
2024-06-21T05:42:30.763Z
So the compiler ends up figuring out the right order? Also, regarding "it can be overridden in the compiler invocation", could you explain how or point to documentation? I couldn't find 
a relevant option in the documentation of the `go` command. The GCC Go front-end's [invocation description](https://gcc.gnu.org/onlinedocs/gccgo/Invoking-gccgo.html) says that you have to pass it all the files. Is that how the order may be specified (with `go` giving `gcc` the files in whatever order by default)?
texdr.aft
CC BY-SA 4.0
2024-06-21T05:49:52.933Z
My understanding is that *if* you provide a list of filenames on the command line then those are taken in that order, but that is not a typical way to invoke it now — generally you just ask `go` to build and it finds all usable files within the source root (and sorts them). An alternative implementation would be permitted to do something different while complying with the specification if no order is specified.
Michael Homer
CC BY-SA 4.0
2024-06-21T18:35:06.963Z
@texdr.aft the normal thing is to use `go build` on a package or list of packages, in which case the files in each package get passed to the compiler in lexical order. However that's a tooling detail that's outside of the scope of the language spec, and you can invoke `go build` or `go tool compile` with a list of filenames. The spec only pins down behavior wrt the ordering which the compiler sees, but *encourages* build tools to make that order lexical filename order. It's just a wrinkle, not anything another language should reproduce exactly.
hobbs
CC BY-SA 4.0
2024-06-21T18:37:37.963Z
(it also only matters if you're doing weirdly cyclic things with variable initializations from functions with side-effects or multiple `init()`s in a package, which are probably not a great idea regardless).
hobbs
<blockquote>
<p>For now I don't plan on having the compiler operate on anything other than complete modules.</p>
</blockquote>
<p>Frame challenge: if the compiler has to read the complete source of a module every time any file in the module is changed, this will limit the size of modules, simply because people will get bored of waiting for them to compile, and will split them up.</p>
<p>This may <em>sound</em> like a good thing, but sometimes it will create artificial constraints that make the language less useful. I work with a domain-specific language that uses modularity in this kind of spirit. The language implementation transpiles source into C, which is then compiled. That forced us to have separate compilation.</p>
<p>Some modules in the product are small, and reading the entire source would not be a problem. But some of them have a couple of hundred large source files, and transpiling and compiling the whole module would take minutes, sometime tens of minutes. But all the files in a large module deal with the same, quite complicated, concepts and data structures in different ways. Splitting up such a module would create artificial divisions and make further development harder.</p>

CC BY-SA 4.0
2024-06-21T21:57:58.943Z
2024-06-22T16:23:26.730Z
2024-06-22T16:23:26.730Z
John Dallman
CC BY-SA 4.0
2024-06-21T22:21:29.150Z
I generally agree. But my language is very dynamic and I feel it would be difficult to compile files in isolation without producing really slow code. Maybe I'm just not trying hard enough :) I think I would have to generate intermediate files rather than final object code.
texdr.aft
CC BY-SA 4.0
2024-06-22T06:17:34.457Z
An intermediate form sounds like the best approach.
John Dallman
<p>Alsys Ada did this in two phases:</p>
<ol>
<li>Code was compiled with the information available, including generating calls to pieces of code expected to be supplied later in other modules.</li>
<li>A special Ada linker - called the binder, and not to be confused with the system linker (provided by DOS, for Alsys Ada) - was run to &quot;bind together&quot; the entire program.  It did all the final semantic checks - making sure all declarations/definitions in different modules lined up - and then generated code itself for things that couldn't be done until the whole program was available - such as instantiating generics for specific types, or providing implementations/tables for type attributes like <code>'Image</code> and <code>'Value</code> iff they were used in the code for specific types.</li>
</ol>
<p>After the binder the system linker was run and it only had to work on one humungous object file + the separate Ada run-time library and then write the DOS-format executable.</p>
<p>(I do get that Alsys Ada isn't an &quot;existing&quot; language implementation as the OP requested - I mean, it <em>exists</em> but isn't <em>current</em>.  But it was interesting enough - with its binder - that I thought I could mention it.)</p>

CC BY-SA 4.0
2024-06-21T16:41:37.633Z
2024-06-21T16:41:37.633Z
davidbak
CC BY-SA 4.0
2024-06-21T16:56:00.417Z
You've seen my posts on Retrocomputing.SE—don't worry, age or obsolescence doesn't bother me! As a matter of fact, my language is a bit of an Algol 68 revival, with bold words, delimiters spelled backwards, and a huge character set. Your answer along with @feldentm's have led me to consider compiling files into stubs instead of all at once. (Not sure if I'll do that but I like the diversity of suggestions.)
texdr.aft
CC BY-SA 4.0
2024-06-21T17:25:36.007Z
I should also note that with this approach you generally have to accept a bit of pessimism in generated code - at the calling site to the separate module - since you can't inline that code you haven't seen.  You the user or the alternative you the language implementor have to judge those costs vs the use cases.  To go beyond that you're usually in the realm of full program LTCG.
davidbak
CC BY-SA 4.0
2024-06-21T17:28:03.120Z
I should also add (someday I'll edit the answer directly!) that in Ada these separate things could be quite extensive!  E.g., a record could be declared - and used! - and only when the binder saw the implementation would the language system know that the record a) had dynamically sized components or b) included tasks (which required special initialization, finalization code at each site the record was created or deleted!).  (Bonus for: dynamic-sized array of tasks ...). So _each_ record decl/use in this way basically had to do the callouts if the defn was separate ...
davidbak
CC BY-SA 4.0
2024-06-21T17:56:15.293Z
Do you think it was done that way because of limited memory?
texdr.aft
CC BY-SA 4.0
2024-06-21T17:57:02.187Z
Which thing?  ...
davidbak
CC BY-SA 4.0
2024-06-21T17:58:57.357Z
Producing stub files that get bound in a separate step.
texdr.aft
CC BY-SA 4.0
2024-06-23T11:39:09.667Z
GNAT which is an active Ada compiler also has a binder - see [4.5. Binding with gnatbind](https://docs.adacore.com/gnat_ugn-docs/html/gnat_ugn/gnat_ugn/building_executable_programs_with_gnat.html#binding-with-gnatbind). Part of the description is ``In particular, error messages are generated if a program uses inconsistent versions of a given unit.`` which is similar to that described for Alsys Ada.
Chester Gillon
CC BY-SA 4.0
2024-06-23T23:26:31.620Z
@davidbak As I recall, in Ada, a record whose type is accessible outside a package is declared and defined in the package header, with the definition possibly tucked in the private section, and that this was done specifically to avoid what you're mentioning. I've only used GNAT and never the 1983 version of Ada, but I was under the impression that was true for all standard versions of Ada.
prosfilaes
<p><a href="https://github.com/tyr-lang" rel="nofollow noreferrer">Tyr</a> has been like that from the very beginning. The definitions in files can be distributed amongst files in the same module without changing the semantics.</p>
<p>The solution here is to merge the ASTs from parsing individual files into a module AST before starting semantic analysis. The exact order of this is deliberately unspecified and the language is carefully designed to avoid all situations where this would make a difference. I.e. such a language must avoid top-down elaboration at compiletime and at runtime. For instance having init functions like in Go breaks this experience. The same issue arises with static initialization in Java which is also top-down.</p>
<p>Another benefit of Tyr's approach over Go's is that files (what Go calls packages) within a library can have cyclic dependencies. This is required in larger projects to structure large groups of data types.</p>
<p>The hardest part when avoiding top-down elaboration is that some sort of static dependency tracking must be implemented. This dependency tracking must be done at a granularity and precision that allows to express most common initializers. Otherwise, users will just workaround it and suffer as known from Ada's and Go's package-based elaboration.</p>
<p>C and C++ build systems in the past worked by enumerating files and I cannot remember anyone saying that that's a good thing. So, enumerating the files explicitly is likely perceived like a waste of time by most programmers. Also, from my experience, changing the semantics of a program by reordering the compilation order is not perceived as a good thing either. Instead, it is usually called an ODR violation even though it might not be in a strict sense.</p>

CC BY-SA 4.0
2024-06-21T13:30:53.517Z
2024-06-21T13:30:53.517Z
feldentm
# How do languages where multiple files make up a module handle combining them into one translation/compilation unit?
Posted by: username_0 on 2024-06-21
Mainly, I want to know what existing languages/implementations that use this sort of multi-file module structure do.

**In my language, modules can consist of multiple files**. (For now I don't plan on having the compiler operate on anything other than complete modules.) A module's effective source text is roughly\* a concatenation of the text of the files. For the sake of this post, let's just say that the “top level” of a module contains declarations that may or may not be exported from the module. A module declaration is necessary for the compiler to associate a name with the module, which is used when referring to exported things. I haven't decided whether I want the files to be specified explicitly (in the module declaration) or if they are implicit (based on directory structure). The latter choice is more conventional but less portable (can't do it with the standard C library). **Specifying files would solve the problem that this question is about**, although I'm not aware of any languages that do it (except for Common Lisp if you consider ASDF to be its module system).

**When a module has more than one file, the compiler needs to read all the files.** In general in my language, the order of declarations doesn't matter, but there are edge cases when it does. **In what order should the files be read/processed?** Here are the choices I've thought of.

* Leave it unspecified; portable code shouldn't depend on the order. Fine in concept but there are some issues. First, it *is* possible for declarations to have different semantics depending on the order in which they appear, and the compiler can't always tell when this is the case. Thus the user is left to enforce the requirement. The compiler still needs to decide on an order; directory order is probably the simplest.
* Directory order, assuming the files are specified implicitly. It isn't portable and I believe means that a program's compilation might break if you move it to a different file system. It is also not easily controllable by users.
* Specification order, if files are explicitly listed. This gives the user control over the order and means that source files can be located anywhere; it's also generally portable between most platforms, as long as the files aren't in system-specific locations.

I like the idea of specifying files, but again I believe it's uncommon. Perhaps it should be *possible* to specify the files if you need the control and the compiler defaults to directory order.

I'm glossing over details like where the module declaration goes. Also note that I'm not talking about the order of compilation of other modules.

If I understand correctly, Rust's module system is similar to what I want for my language. I couldn't find anything about the Rust compiler's file processing order.

---

\* A related concern is whether the files are simplistically concatenated as with the C preprocessor's `#include` or parsed as complete sequences of declarations and then combined somehow. If I go with dumb concatenation, then constructing examples where order matters is much easier. However, I might require an “in module” declaration in each constituent file, which would effectively require files to be syntactically complete. I'm leaning towards parsing files separately, since it seems the more “modern” choice.
**Tags:** compilers implementation modules
> **Comments:**
> **username_1**: It *seems* like what you are describing is precisely the Go package system. What are you looking for an answer to provide here - existing analogous models, design research, general discussion? That is, what makes for a correct answer to this question?  
> **username_0**: @MichaelHomer Mainly I want to know what existing languages/implementations do. I don't know Go, but from cursory research it seems you can split a package between files by putting the package declaration in every file. Is the compiler able to compile each file separately? That isn't really an option for my language. (Not that that invalidates the comparison.)  
> **username_1**: Ok, I've made an edit to try to make that clear (so that it doesn't sound like a bikeshed, opinionated question, but looking for actual existing manifestations of this pattern). Please adjust if I've missed your meaning somewhere.  
> **username_2**: I'm not sure if I understand: is file processing order semantically meaningful in your language? Because if it is, you have to let the user choose and define that order. And if it is not, it is irrelevant whether you use directory order or not.  
> **username_0**: @EldritchConundrum It is meaningful but only in the sense that the order of declarations is meaningful and files in a module contain declarations.  
> **username_3**: @EldritchConundrum Not necessarily, static initialization in Java depends on order but cannot be specified either.  
> **username_3**: @texdr.aft you should think about removing all properties of your language that make the order of declarations meaningful. It improves usability, simplifies refactorings and makes tool support easier to implement.  
> **username_4**: Do you have examples where the order of declarations matters? Either for your language specifically or in another language. Maybe I'm missing something obvious but it seems trivial to disallow most/all of these (at least in most languages I'm familiar with).  
> **username_0**: @CoffeeTableEspresso Initialization code might have side effects. Also outer declarations can be shadowed. (I know shadowing is controversial but I want it.)  
> **username_5**: It's important to distinguish the concepts of "edge cases where order of declaration may affect program behavior" and "edge cases where the order of declaration *matters*".  There are many scenarios where several observably-different program behaviors would be equally acceptable, and the fact that declaration order might affect which behavior is chosen would not imply that one nail down rules for how it does so as opposed to recognizing that certain choices are made in "unspecified" fashion.  
> **username_4**: @texdr.aft I personally don't see a use for shadowing declarations at the top level like that. For the initialization example, when is initialization done? At compile time? At program start up?  
---
## 5 answers
### Answer by username_6 on 2024-06-21. Score: 7
Frame challenge:

> In general in my language, the order of declarations doesn't matter, but there are edge cases when it does.

That's not great. If "independent" units aren't independent, then it makes it hard for *humans* to make sense of things. If your compiler can't figure out what the semantics of loading the files in different orders should be, how would a programmer be able to do it? If the behavior is different depending on which order they're combined, how does the human reading the code know which behavior is the intended one?

This is a particular challenge if it's just "edge cases" where it matters. If 99+% of the time the combination is order independent, but there's strange 1% cases where it does, then a normal (non-expert) programmer will likely only be aware of the 99% behavior and wouldn't necessarily be on the lookout for the 1% edge case. They'll they'll possibly run into bugs where the compiler chose the one order, but they're implicitly expecting the other behavior.

Since you have control of the semantics of your programming language, I would highly recommend changing things such that you remove these edge cases. It's likely worth it to go through contortions (on the compiler side) to make that happen. You don't specify which edge cases you're running into, but approaches like having a two-phase parse (e.g. one phase just to pull out what definitions are available and then the second phase to actually use those definitions) might work for your issues. -- Again, think about how a programmer would determine what the intended behavior is, and try to make the compiler "intelligent" enough to pick that up.

If the order dependence is something more along the lines of multiple incompatible definitions for the same name (or something similar where there's contradictory programmer intent), then I'd highly recommend not trying to automatically resolve it. If the programmer isn't being clear, throw up a compiler error. Implicit rules where certain parts of the program are ignored or overwritten because of arbitrary order dependency issues are horrible to debug. (Why isn't the function returning the value it's supposed to? Because the compiler isn't calling that function, it's calling some other function you're not even aware of. Why? Because someone else renamed an unrelated file from `user_account.xyz` to `customer_account.xyz`, and since 'c' comes before most of the rest of the alphabet, the compiler is prioritizing those functions.) -- The compiler often doesn't need to determine an order, the compiler can simply kick the issue back to the programmer to resolve properly.

I'm guessing the issues you're having with order dependence can be resolved either by putting a bit more "intelligence" into your compiler, or by simply refusing to compile an order-dependent program. If, for some reason you can't resolve the issues in those cases (e.g. because there's no way for the programmer to rewrite the program to avoid them, or the compiler fundamentally can't be adjusted to support intelligent determination or error detection), then it's probably best to make the order dependence explicit in all cases, not just the edge cases. That way you don't have programmers who have standard work patterns for the 99+% cases, but then run into hairy debugging issues when the edge cases eventually pop up.
> **Comments:**
> **username_0** (2024-06-21): Hmm... good points here. I'll probably have a way for the programmer to say "hey I'm gonna shadow this binding" and otherwise the compiler will tell them "hey just so you know you're shadowing this binding". But how might initialization be handled in an order independent way? In `x: int := f(); y: int := g()`, if `f` and `g` can have side effects then it may make a difference to have `x` after `y`. Tracking dependencies isn't always possible.  
> **username_6** (2024-06-21): @texdr.aft Again, if side effects matter, how does a human know what should happen? -- One is to require initializer functions like f() and g() be without side effects. This could be explicitly checked (by having special "pure" functions either in the language proper or in the compiler internals). Or you could require non-trivial initialization to happen explicitly in standard control flow. (Rather than implicitly "before the program starts".) Or you could take the C/C++ route and say "order-dependent static initialization is undefined behavior: don't count on _either_ order."  
> **username_4** (2024-06-21): @R.M. small correction, it's only a problem  in C++,  not C. C doesn't allow you to initialize static variables with just anything, just with constants.  
> **username_1** (2024-06-21): Since the question is seeking **instances of existing systems that do this**, this is not an answer to it (if it were an answer, the question would likely be off-topic). It could be edited to give supporting evidence of such systems being tried without success, for example, and make this case.  
> **username_4** (2024-06-26): @MichaelHomer I think C++'s "order-dependent static initialization" is a pretty good example of an existing system that does this, and it causes endless problems  
> **username_1** (2024-06-26): @CoffeeTableEspresso I don't disagree that this is probably a bad idea when it's avoidable, but the answer as currently put doesn't back itself up - it's reckons on a background point, but the question seeks experience from other languages that have had this. We don't allow "rate my language design" questions, and that's what this would be an answer to currently (admittedly, there is a lot of excess detail in the question body). The answer could be reframed in light of negative examples like that and make this case, using C++/other examples to address the question: they do it *poorly* because…  
---
### Answer by username_1 on 2024-06-21. Score: 3
I think that what you're describing is almost exactly the [Go package system](https://go.dev/ref/spec#Package_initialization):

* Declarations from multiple files are combined into a single unit.
* Each file has a `package` header specifying which package it belongs to.
* There is no defined ordering between files, and no way to specify one in the code.
* Declarations from any file in the package are visible throughout all files.
* Ordering is visible in two ways:
  + Declared variables are initialised in a topological order across all files, evaluating only after all manifest dependencies are initialised.
  + There may be multiple `init` functions, which are all executed after the variables are initialised.
* The compiler identifies all available Go source files within the source root, and collates them by the package declaration.
  + Individual files have no meaning and cannot be processed apart from their module.
* Directory structuring is conventional, but the package declaration is definitive.

In practice, the ordering between files will most often be lexically by filename, but as far as I know that is not *required*, and it can be overridden in the compiler invocation. The *other* steps make this order largely irrelevant, and are really how it is able to get away with this multiple-file approach. You can observe the file ordering with side-effecting initialisation code, but it's hard to have order-dependent *problems*. However, it isn't completely impossible to craft a scenario that goes wrong.

Subject to topological constraints, top-to-bottom initialisation is favoured. This means that if you have several variables, spread between files, some of which depend on some variable from another file, you'd expect the topmost variable with no dependencies in the lexicographically earliest file to evaluate first, then all the other zero-dependency variables, then the topmost variable in the earliest file that depended only on variables that have already been initialised, and so on. This topological ordering eliminates almost all of the potential ordering complications that could arise from any ordering of the module files.

`init()` functions are executed after all variables initialisations have run, in the same source order as zero-dependency variables. I *believe* that at one point their order was explicitly unspecified, so this has been a change (although I think it may codify what the reference compiler always did). They can rely on all declared variables with initialisers throughout the entire package having been evaluated already. It is *possible* to create bad orderings this way, but it's not going to happen in the typical cases.

Go's package model has had some controversy over time, both to do with potential variations in initialisation order and its insistence on tree-structured (acyclic) imports. There's also been a later *module* system overlaid on it at a higher level; a module is a collection of packages.

Notwithstanding that, this is a practical realisation of pretty much exactly the approach you're describing and it's been around for a while, so there is a lot of practical experience with it and plenty of code written using it. You can find the complaints people have about it and see whether they are impactful for you. Overall, it seems to work just fine for many or most cases.
> **Comments:**
> **username_0** (2024-06-21): So the compiler ends up figuring out the right order? Also, regarding "it can be overridden in the compiler invocation", could you explain how or point to documentation? I couldn't find 
a relevant option in the documentation of the `go` command. The GCC Go front-end's [invocation description](https://gcc.gnu.org/onlinedocs/gccgo/Invoking-gccgo.html) says that you have to pass it all the files. Is that how the order may be specified (with `go` giving `gcc` the files in whatever order by default)?  
> **username_1** (2024-06-21): My understanding is that *if* you provide a list of filenames on the command line then those are taken in that order, but that is not a typical way to invoke it now — generally you just ask `go` to build and it finds all usable files within the source root (and sorts them). An alternative implementation would be permitted to do something different while complying with the specification if no order is specified.  
> **username_7** (2024-06-21): @texdr.aft the normal thing is to use `go build` on a package or list of packages, in which case the files in each package get passed to the compiler in lexical order. However that's a tooling detail that's outside of the scope of the language spec, and you can invoke `go build` or `go tool compile` with a list of filenames. The spec only pins down behavior wrt the ordering which the compiler sees, but *encourages* build tools to make that order lexical filename order. It's just a wrinkle, not anything another language should reproduce exactly.  
> **username_7** (2024-06-21): (it also only matters if you're doing weirdly cyclic things with variable initializations from functions with side-effects or multiple `init()`s in a package, which are probably not a great idea regardless).  
---
### Answer by username_8 on 2024-06-21. Score: 2
> For now I don't plan on having the compiler operate on anything other than complete modules.

Frame challenge: if the compiler has to read the complete source of a module every time any file in the module is changed, this will limit the size of modules, simply because people will get bored of waiting for them to compile, and will split them up.

This may *sound* like a good thing, but sometimes it will create artificial constraints that make the language less useful. I work with a domain-specific language that uses modularity in this kind of spirit. The language implementation transpiles source into C, which is then compiled. That forced us to have separate compilation.

Some modules in the product are small, and reading the entire source would not be a problem. But some of them have a couple of hundred large source files, and transpiling and compiling the whole module would take minutes, sometime tens of minutes. But all the files in a large module deal with the same, quite complicated, concepts and data structures in different ways. Splitting up such a module would create artificial divisions and make further development harder.
> **Comments:**
> **username_0** (2024-06-21): I generally agree. But my language is very dynamic and I feel it would be difficult to compile files in isolation without producing really slow code. Maybe I'm just not trying hard enough :) I think I would have to generate intermediate files rather than final object code.  
> **username_8** (2024-06-22): An intermediate form sounds like the best approach.  
---
### Answer by username_9 on 2024-06-21. Score: 1
Alsys Ada did this in two phases:

1. Code was compiled with the information available, including generating calls to pieces of code expected to be supplied later in other modules.
2. A special Ada linker - called the binder, and not to be confused with the system linker (provided by DOS, for Alsys Ada) - was run to "bind together" the entire program. It did all the final semantic checks - making sure all declarations/definitions in different modules lined up - and then generated code itself for things that couldn't be done until the whole program was available - such as instantiating generics for specific types, or providing implementations/tables for type attributes like `'Image` and `'Value` iff they were used in the code for specific types.

After the binder the system linker was run and it only had to work on one humungous object file + the separate Ada run-time library and then write the DOS-format executable.

(I do get that Alsys Ada isn't an "existing" language implementation as the OP requested - I mean, it *exists* but isn't *current*. But it was interesting enough - with its binder - that I thought I could mention it.)
> **Comments:**
> **username_0** (2024-06-21): You've seen my posts on Retrocomputing.SE—don't worry, age or obsolescence doesn't bother me! As a matter of fact, my language is a bit of an Algol 68 revival, with bold words, delimiters spelled backwards, and a huge character set. Your answer along with @feldentm's have led me to consider compiling files into stubs instead of all at once. (Not sure if I'll do that but I like the diversity of suggestions.)  
> **username_9** (2024-06-21): I should also note that with this approach you generally have to accept a bit of pessimism in generated code - at the calling site to the separate module - since you can't inline that code you haven't seen.  You the user or the alternative you the language implementor have to judge those costs vs the use cases.  To go beyond that you're usually in the realm of full program LTCG.  
> **username_9** (2024-06-21): I should also add (someday I'll edit the answer directly!) that in Ada these separate things could be quite extensive!  E.g., a record could be declared - and used! - and only when the binder saw the implementation would the language system know that the record a) had dynamically sized components or b) included tasks (which required special initialization, finalization code at each site the record was created or deleted!).  (Bonus for: dynamic-sized array of tasks ...). So _each_ record decl/use in this way basically had to do the callouts if the defn was separate ...  
> **username_0** (2024-06-21): Do you think it was done that way because of limited memory?  
> **username_9** (2024-06-21): Which thing?  ...  
> **username_0** (2024-06-21): Producing stub files that get bound in a separate step.  
> **username_10** (2024-06-23): GNAT which is an active Ada compiler also has a binder - see [4.5. Binding with gnatbind](https://docs.adacore.com/gnat_ugn-docs/html/gnat_ugn/gnat_ugn/building_executable_programs_with_gnat.html#binding-with-gnatbind). Part of the description is ``In particular, error messages are generated if a program uses inconsistent versions of a given unit.`` which is similar to that described for Alsys Ada.  
> **username_11** (2024-06-23): @davidbak As I recall, in Ada, a record whose type is accessible outside a package is declared and defined in the package header, with the definition possibly tucked in the private section, and that this was done specifically to avoid what you're mentioning. I've only used GNAT and never the 1983 version of Ada, but I was under the impression that was true for all standard versions of Ada.  
---
### Answer by username_3 on 2024-06-21. Score: 1
[Tyr](https://github.com/tyr-lang) has been like that from the very beginning. The definitions in files can be distributed amongst files in the same module without changing the semantics.

The solution here is to merge the ASTs from parsing individual files into a module AST before starting semantic analysis. The exact order of this is deliberately unspecified and the language is carefully designed to avoid all situations where this would make a difference. I.e. such a language must avoid top-down elaboration at compiletime and at runtime. For instance having init functions like in Go breaks this experience. The same issue arises with static initialization in Java which is also top-down.

Another benefit of Tyr's approach over Go's is that files (what Go calls packages) within a library can have cyclic dependencies. This is required in larger projects to structure large groups of data types.

The hardest part when avoiding top-down elaboration is that some sort of static dependency tracking must be implemented. This dependency tracking must be done at a granularity and precision that allows to express most common initializers. Otherwise, users will just workaround it and suffer as known from Ada's and Go's package-based elaboration.

C and C++ build systems in the past worked by enumerating files and I cannot remember anyone saying that that's a good thing. So, enumerating the files explicitly is likely perceived like a waste of time by most programmers. Also, from my experience, changing the semantics of a program by reordering the compilation order is not perceived as a good thing either. Instead, it is usually called an ODR violation even though it might not be in a strict sense.
---
