Since @import can’t take a comptime string, instead expecting a string literal defined directly within its call, it must be that it has to resolve the given path and import the respective source file strictly before compile-time evaluation phase, but I don’t see why it can’t complete all the comptime stuff first and then run all the @import calls.
My understanding is that this is a very deliberate design decision.
@import is actually a bit of syntax mascaraing as a built-in function call. It is a reverse syntax sugar of sorts, as the semantics are as if you’d have to write import "std" as std;.
This is needed for reasonable compilation model. The set of @import calls reachable from the root file determine the set of files that must be compiled. It is not absolutely required, but very much desirable that you can compute this set of files without actually doing any language analysis.
So, in Zig as it is, you can compute this set after just parsing files individually. I think that not even full parsing is required, just line-by-line lexing would be enough.
That means that Zig can quickly and discover the full set of files, so that parsing can be parallelised.
You could allow some amount of comptime evaluation here, but that would introduce a sequence point into the compiler’s pipeline — before starting to parse the next file, it must do some comptime eval of the current file.
Rust works that way! There, discovering files included through mod foo;, expanding macros, and resolving imports all happen at the same time. This creates massive problems for incremental compilation, which can be surmounted only with a huge increase in compiler’s complexity, and, even then, the result is not as parallel and as incremental as the Zig’s version, it’s just a touch better than trivial sequential compillation.
That’s what I thought really, and I’m both hands for the idea of enabling, in the most elegant way, the ultimate compiler superpower that is incremental compilation.
But, I guess, I just thought this could be done at compile-time because the @import’s signature and documentation don’t mention that @import is a special builtin, a call to which must be resolved even before semantic analysis, that is, during parsing. That distinction isn’t mentioned, so it threw me off.
I wonder how it can be added, because the current signature says the path parameter is just a comptime string. If this is an exceptional case, I guess it could be explained as a sidenote, but I also wonder if there’re, in general, more things to play around with at parsetime
I’d say the time to revisit this, if it needs revisiting, is after incremental compilation. That will require / enable caching a lot of state about the project, and translating a diff into the minimal action set which the compiler needs to take.
Meaning that the compiler can amortize the cost of resolving a comptime string into an @import statement across every build in which that string doesn’t change. Done right, incremental compilation should mean we can afford more expensive comptime stuff, rather than less. But keeping the model streamlined will help get that system built in the first place.
FYI import used to accept comptime strings, it got changed at some point during the last year or so. It’s not just incremental, not having to solve comptime to know the full “abstract import tree” is useful for all kinds of tooling (eg autodocs).
So, since it was there and was removed, don’t expect it to return :^)
In simple situations you can trivially switch above the import expression and hardcode the path for each case:
No expectations one way or the other, really. It’s never even occurred to me to use anything but a literal string for an import statement, it doesn’t seem like a big loss.
If we leave out the expectations part, “anything removed will never come back” doesn’t strike me as a useful or invariably correct premise. Async, for example, may or may not come back, but removing it didn’t imply either of those outcomes.
That’s the idea here: with incremental compilation, the system is going to be caching ‘solved’ comptime already, surely. I can imagine that being useful for all sorts of tooling, which otherwise have to try and figure it out themselves, one by one, and since at the limit, comptime is solved by executing it, that seems particularly inefficient and error prone compared to tapping in to however the compiler handles that sort of thing.
Because it’s a recurring problem, tools treating Zig as though it doesn’t have comptime, because comptime is hard for tools. Solving it by removing everything tools have problems with just means no comptime, at the limit.
I’m talking based on experience being involved with the Zig project, not making universal logical assertions.
The fact that comptime-arg import was there and then removed means that the tradeoff was considered and not found to be the best choice, which is qualitatively very different than something not having been considered yet, or even having been considered but never implemented.
Async was not removed in this same sense. It was implemented in stage1 but that implementation was never ported to stage2.
And yet Zig tries hard to be a programming language with good greppability, the ultimate low-tech programming tool. Non-comptime imports also means that within a module you can grep to know all potential import locations for a given file.
Just a note:
Something I did in one project was to generate a source file as a build step and made it importable. Also, I have found it helpful to make good use of addOptions in the build file.