Hacker News new | ask | show | jobs
by msteffen 419 days ago
If I understand TFA correctly, the author claims that D’s approach is actually different: https://matklad.github.io/2025/04/19/things-zig-comptime-won...

“In contrast, there’s absolutely no facility for dynamic source code generation in Zig. You just can’t do that, the feature isn’t! [sic]

Zig has a completely different feature, partial evaluation/specialization, which, none the less, is enough to cover most of use-cases for dynamic code generation.”

4 comments

The partial evaluation/specialization is accomplished in D using a template. The example from the link:

    fn f(comptime x: u32, y: u32) u32 {
        if (x == 0) return y + 1;
        if (x == 1) return y * 2;
        return y;
    }
and in D:

    uint f(uint x)(uint y) {
        if (x == 0) return y + 1;
        if (x == 1) return y * 2;
        return y;
    }
The two parameter lists make it a function template, the first set of parameters are the template parameters, which are compile time. The second set are the runtime parameters. The compile time parameters can also be types, and aliased symbols.
Here is, I think, an interesting example of the kind of thing TFA is talking about. In case you’re not already familiar, there’s an issue that game devs sometimes struggle with, where, in C/C++, an array of structs (AoS) has a nice syntactic representation in the language and is easy to work with/avoid leaks, but a struct of arrays (SoA) has a more compact layout in memory and better performance.

Zig has a library to that allows you to have an AoS that is laid out in memory like a SoA: https://zig.news/kristoff/struct-of-arrays-soa-in-zig-easy-i... . If you read the implementation (https://github.com/ziglang/zig/blob/master/lib/std/multi_arr...) the SoA is an elaborately specialized type, parameterized on a struct type that it introspects at compile time.

It’s neat because one might reach for macros for this sort of the thing (and I’d expect the implementation to be quite complex, if it’s even possible) but the details of Zig’s comptime—you can inspect the fields of the type parameter struct, and the SoA can be highly flexible about its own fields—mean that you don’t need a macro system, and the Zig implementation is actually simpler than a macro approach probably would be.

D doesn't have a macro system, either, so I don't understand what you mean.
IIUC, it does have code generation—the ability to generate strings at compile-time and feed them back into the compiler.

The argument that the author of TFA is making is that Zig’s comptime is a very limited feature (which, they argue, is good. It restricts users from introducing architecture dependencies/cross-compilation bugs, is more amenable to optimization, etc), and yet it allows users to do most of the things that more general alternatives (such as code generation or a macro system) are often used for.

In other words, while Zig of course didn’t invent compile-time functions (see lisp macros), it is notable and useful from a PL perspective if Zig users are doing things that seem to require macros or code generation without actually having those features. D users, in contrast, do have code generation.

Or, alternatively, while many languages support metaprogramming of some kind, Zig’s metaprogramming language is at a unique maxima of safety (which macros and code generation lack) and utility (which e.g. Java/Go runtime reflection, which couldn’t do the AoS/SoA thing, lack)

Edit Ok, I think Zig comptime expressions are just like D templates, like you said. The syntax is nicer than C++ templates. Zig’s “No host leakage” (to guarantee cross-compile-ability) looks like the one possibly substantively different thing.

> Zig’s “No host leakage” (to guarantee cross-compile-ability) looks like the one possibly substantively different thing.

That is a good idea, but could be problematic if one relies on size_t, which changes in size from 32 to 64 bit. D's CTFE adds checks for undefined behavior, such as shifting by more bits than are in the type being shifted. These checks are not done at runtime for performance reasons.

D's CTFE also does not allow calling the operating system, and only works on functions that are "pure".

Because Zig supports cross-compilation, what you care about isn't the host -- the machine that runs the compiler -- but the target, which is not (necessarily) the same as the host. While information about the host isn't made available, information about the compilation target is: https://ziglang.org/documentation/master/#Compile-Variables
Using a different type vs. a different syntax can be an important usability consideration, particularly since D also has templates and other features, where Zig provides only the comptime type for all of them. Homogeneity can also be a nice usability win, though there are downsides as well.
Zig's use of comptime in a function argument makes it a template :-/

I bet if you use such a function with different comptime arguments, compile it, and dump the assembler you'll see that function appearing multiple times, each with somewhat different code generated for it.

> Zig's use of comptime in a function argument makes it a template :-/

That you can draw an isomorphism between two things does not mean they are ergonomically identical.

When we're responding to quite valid points about other languages having essentially the same features as Zig with subjective claims about ergonomics, the idea that Zig comptime is "revolutionary" is looking awfully flimsy. I agree with Walter: Zig isn't doing anything novel. Picking some features while leaving others out is something that every language does; if doing that is enough to make a language "revolutionary", then every language is revolutionary. The reality is a lot simpler and more boring: for Zig enthusiasts, the set of features that Zig has appeals to them. Just like enthusiasts of every programming language.
> Picking some features while leaving others out is something that every language does; if doing that is enough to make a language "revolutionary", then every language is revolutionary.

Picking a set of well motivated and orthogonal features that combine well in flexible ways is definitely enough to be revolutionary if that combination permits expressive programming in ways that used to be unwieldy, error-prone or redundant, eg. "redundant" in the sense that you have multiple ways of expressing the same thing in overlapping but possibly incompatible ways. It doesn't follow that every language must be revolutionary just because they pick features too, there are conditions to qualify.

For systems programming, I think Zig is revolutionary. I don't think any other language matches Zig's cross-compilation, cross-platform and metaprogramming story in such a simple package. And I don't even use Zig, I'm just a programming language theory enthusiast.

> I agree with Walter: Zig isn't doing anything novel.

"Novel" is relative. Anyone familiar with MetaOCaml wouldn't have seen Zig as particularly novel in a theoretical sense, as comptime is effectively a restricted multistage language. It's definitely revolutionary for an industry language though. I think D has too much baggage to qualify, even if many Zig expressions have translations into D.

> Picking some features while leaving others out is something that every language does; if doing that is enough to make a language "revolutionary", then every language is revolutionary.

You can say that about the design of any product. Yet, once in a while, we get revolutionary designs (even if every feature in isolation is not completely novel) when the choice of what to include and what to leave out is radically different from other products in the same category in a way that creates a unique experience.

>for Zig enthusiasts, the set of features that Zig has appeals to them. Just like enthusiasts of every programming language.

I find it rather amusing that it's a Java and a Rust enthusiast who are extolling Zig approach here! I am not particularly well read with respect to programming languages, but I don't recall many languages which define generic pair as

    fn Pair(A: type, B: type) type {
        return struct { fst: A, snd: B };
    }
The only one that comes to mind is 1ML, and I'd argue that it is also revolutionary.
I'm sorry, but not being able to see that a design that uses a touchscreen to eliminate the keyboard is novel despite the touchscreen itself having been used elsewhere alongside a keyboard, shows a misunderstanding of what design is.

Show me the language that used a general purpose compile-time mechanisms to avoid specialised features such as generics/templates, interfaces/typeclasses, macros, and conditional compilation before Zig, then I'll say that language was revolutionary.

I also find it hard to believe that you can't see how replacing all these features with a single one (that isn't AST macros) is novel. I'm not saying you have to think it's a good idea -- that's a matter of personal taste (at least until we can collect more objective data) -- but it's clearly novel.

I don't know all the languages in the world and it's possible there was a language that did that before Zig, but none of the languages mentioned here did. Of course, it's possible that no other language did that because it's stupid, but that doesn't mean it's not novel (especially as the outcome does not appear stupid on the face of it).

that's a comically archaic way of using the verb 'to be', not a grammatical error. you see it in phrases like "to be or not to be", or "i think, therefore i am". "the feature isn't" just means it doesn't exist.
Damn, beat me by half a day.
Sure, CTFE can be used to generate strings, then later "mixed-in" as source code, but also can be used to execute normal functions and then the result can be stored in a compile-time constant (in D that's the `enum` storage class), for example generating an array using a function literal called at compile-time:

   enum arr = { return iota(5).map!(i => i * 10).array; }();
   static assert(arr == [0,10,20,30,40]);
> the feature isn’t! [sic]

To be, or not to be... The feature is not.

(IOW, English may not be the author's native language. I'm fairly sure it means "The feature doesn't exist".)