[HN Gopher] Zen-C: Write like a high-level language, run like C
       ___________________________________________________________________
        
       Zen-C: Write like a high-level language, run like C
        
       Author : simonpure
       Score  : 146 points
       Date   : 2026-01-12 12:57 UTC (10 hours ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | ethin wrote:
       | Am I the only one who saw this syntax and immediately though
       | "Man, this looks almost identical to Rust with a few slight
       | variations"?
        
         | CupricTea wrote:
         | It seems to just be Rust for people who are allergic to using
         | Rust.
         | 
         | It looks like a fun project, but I'm not sure what this adds to
         | the point where people would actually use it over C or just
         | going to Rust.
        
           | nnevatie wrote:
           | > what this adds
           | 
           | I guess the point is what is subtracts, instead - answer
           | being the borrow-checker.
        
             | Ygg2 wrote:
             | So it re-adds manual lifetime checking. Got it.
        
               | askyng wrote:
               | It might or might not be a toy project, I'm not sure, but
               | one advantage of subtracting the borrow checking is that
               | the compiler avoids a lot of complex machinery.
               | 
               | Borrow checking in Rust isn't sound AFAIK, even after all
               | these years, so some of the problems with designing and
               | implementing lifetimes, region checking, and borrow
               | checking algorithms, aren't trivial.
        
             | petcat wrote:
             | > answer being the borrow-checker
             | 
             | There is an entire world in Rust where you never have to
             | touch the borrow-checker or lifetimes at all. You can just
             | clone or move everything, or put everything in an Arc
             | (which is what most other languages are doing anyway). It's
             | very easy to not fight the compiler if you don't want to.
             | 
             | Maybe the real fix for Rust (for people that don't want to
             | care), is just a compiler mode where everything is Arc-by-
             | default?
        
           | the__alchemist wrote:
           | Maybe take the parts of rust the author likes, but still
           | encourages pointers in high level operations?
        
         | suioir wrote:
         | I thought the same and felt it looked really out of place to
         | have I8 and F32 instead of i8 and f32 when so much else looks
         | just like Rust. Especially when the rest of the types are all
         | lower case.
        
           | unwind wrote:
           | Agreed, that really stood out as a ... questionable design
           | decision, and felt extremely un-ergonomic which seems to go
           | against the stated goals of the language.
        
             | hyperhello wrote:
             | Every language is apparently required to make one specific
             | version of these totally arbitrary choices, like whether to
             | call the keyword function, func, fun, fn, or def. Once they
             | do, it's a foolish inconsistency with everything else. What
             | if the language supported every syntax?
        
         | turbotim wrote:
         | My immediate thought was it looked a lot like Swift
        
       | Gys wrote:
       | Initial commit was 24h ago, 363 stars, 20 forks already. Man,
       | this goes fast.
        
         | worldsavior wrote:
         | Could be bots.
        
           | directmusic wrote:
           | Definitely could be, but the dev has been posting updates on
           | Twitter for a while now. It could be just some amount of hype
           | they have built.
        
           | alexpadula wrote:
           | It's not, it's just how hackernews works. You'll see new
           | projects hit 1k-10k stars in a matter of a day. You can have
           | the best project, best article to you but if everyone else
           | doesn't think so it'll always be at the bottom. Some luck
           | involved too. Bots upvoting a post not organically I doubt is
           | gonna live long on first page.
        
             | worldsavior wrote:
             | The stars are on GitHub, they can come from somewhere else,
             | e.g. the author himself buying stars.
        
               | alexpadula wrote:
               | This is hella common. Companies have too much money to
               | spend.
        
               | Duendek86 wrote:
               | Hi, I'm the developer's father. Trust me, he hasn't
               | bought a single star in his life--not even in Super Mario
               | :p
        
         | saidnooneever wrote:
         | man has been posting a lot before the initial commit about his
         | library. following the guy on linkedin.
        
       | giancarlostoro wrote:
       | Syntax aside, how does this compare to Nim? Nim does similar, I
       | think Crystal does as well? Not entirely sure about Crystal tbh.
       | I guess Nim and Vala, since I believe both transpile to C, so you
       | really get "like C" output from both.
        
         | jdc0589 wrote:
         | man I haven't heard anything about Vala in _ages_. is it still
         | actively developed /used? how is it?
        
           | spookie wrote:
           | Yes, it is actively being developed.
           | 
           | Quite easy to make apps with it and GNOME Builder makes it
           | really easy to package it for distribution (creates a proper
           | flatpak environment, no need to make all the boilerplate).
           | It's quite nice to work with, and make stuff happen. Gtk docs
           | and awful deprecation culture (deprecate functions without
           | any real alternative) are still a PITA though.
        
         | lgunsch wrote:
         | I was also going to mention this reminds me of Vala, which I
         | haven't seen or heard from in 10+ years.
        
           | giancarlostoro wrote:
           | Surprisingly theres a shocking number of GUI programs for
           | Linux made with Vala, and ElementaryOS is built using Vala,
           | and all their custom software uses Vala. So it's not dead,
           | just a little known interesting language. :)
        
         | bloovis wrote:
         | Crystal compiles directly to object code, using LLVM. It does
         | provide the ability to interoperate with C code; as an example,
         | I use this feature to call ncursesw functions from Crystal.
        
       | GrowingSideways wrote:
       | Why not compile to rust or assembly? C seems like an odd choice.
       | 
       | In fact why not simply write rust to begin with?
        
         | ndr wrote:
         | At times people think C is better. See recent discussion about
         | https://sqlite.org/whyc.html
        
           | alexpadula wrote:
           | C is best
        
         | xnacly wrote:
         | Assembly requires way more work than compiling to, say C. Clang
         | and gcc do a lot of the heavy lifting regarding optimisation,
         | spilling values to the stack, etc
        
           | GrowingSideways wrote:
           | Then you're stuck with the C stack, though, and no way to
           | collect garbage.
        
             | saidnooneever wrote:
             | really? you cant track and count your pointers in C? why
             | not?
        
             | UncleEntity wrote:
             | I have a couple interpreters I've been poking at and one
             | uses 'musttail' while the other uses a trampoline to get
             | around blowing up the C stack when dispatching the
             | operators. As for the GC, the trampoline VM has a full-
             | blown one (with bells-and-whistles like an arena for short
             | lived intermediate results which get pushed/popped by the
             | compiled instructions) while the other (a peg parser VM)
             | just uses an arena as the 'evaluation' is short lived and
             | the output is the only thing really needing tracking so
             | uses reference counting to be (easily) compatible with the
             | Python C-API. No worries about the C stack at all.
             | 
             | I mean, I could have used the C stack as the VM's stack but
             | then you have to worry about blowing up the stack, not
             | having access (without a bunch of hackery, looking at you
             | scheme people) to the values on the stack for GC and
             | whatnot and, I imagine, all the other things you have
             | issues with but it's not needed at all, just make your own
             | (or, you know, tail call) and pretend the C one doesn't
             | exist.
             | 
             | And I've started on another VM which does the traditional
             | stack thing but it's constrained (by the spec) to have a
             | maximum stack depth so isn't too much trouble.
        
         | dfox wrote:
         | If I understand the history correctly then it started as a set
         | of C preprocessor macros.
        
       | morcus wrote:
       | An interesting bit to me is that it compiles to (apparently)
       | readable C, I'm not sure how one would use that to their
       | advantage
       | 
       | I am not too familiar with C - is the idea that it's easier to
       | incrementally have some parts of your codebase in this language,
       | with other parts being in regular C?
        
         | zbendefy wrote:
         | i think so. The biggest hurdle with new languages is that you
         | are cut off from a 3rdparty library ecosystem. Being compatible
         | with C 3rd party libraries is a big win.
        
           | actionfromafar wrote:
           | Makes it easy to "try before you buy", too. If you decide
           | it's not for you, you can "step out" and keep the generated C
           | code and go from there.
        
             | morcus wrote:
             | Very good point that I never considered! Thanks.
        
             | forgotpwd16 wrote:
             | This isn't a very sane plan. The ~300 LOC example mini_grep
             | (https://github.com/z-libs/Zen-C/blob/main/examples/tools/m
             | in...) compiles to a ~3.3k LOC monstrosity
             | (https://pastebin.com/raw/6FBSpt1z). It's easier to rewrite
             | the whole thing than going from the generated code.
             | 
             | At least for now, generated code shouldn't be considered
             | something you're ever supposed to interact with.
        
         | saidnooneever wrote:
         | one benefit is that a lot of tooling e.g. for verification etc.
         | is built around C.
         | 
         | another is that it only has C runtime requirement, so no weird
         | runtime stuff to impelement if youd say want to run on bare
         | metal..you could output the C code and compile it to your
         | target.
        
       | v_iter wrote:
       | So, the point of this language is to be able to write code with
       | high productivity, but with the benefit of compiling it to a low
       | level language? Overall it seems like the language repeats what
       | ZIG does, including the C ABI support, manual memory management
       | with additional ergonomics, comptime feature. The biggest
       | difference that comes to mind quickly is that the creator of
       | Zen-C states that it can allow for the productivity of a high
       | level language.
        
         | johnisgood wrote:
         | Nim is a high-level language as well and compiles to C.
        
           | the__alchemist wrote:
           | Odin and Jai are others.
        
             | Voycawojka wrote:
             | Does Odin compile to C? I thought it only uses LLVM as a
             | backend
        
               | sestep wrote:
               | Same question but for Jai.
        
               | johnisgood wrote:
               | No, Odin does not compile to C. It is a standalone
               | programming language that compiles directly to machine
               | code. It primarily uses LLVM as its backend for compiling
               | to machine code, like you said.
        
             | zem wrote:
             | chicken scheme compiles to c as well. it's a pretty
             | convenient compilation target, you get to use all the
             | compilers and tool chains out there and you don't add a
             | dependency on llvm
        
               | dieggsy wrote:
               | I love CHICKEN Scheme! Nice to see it mentioned. Though I
               | think it's worth pointing out it compiles to something
               | pretty far from handwritten C, to my understanding. I
               | think this is true of both performance and semantics; for
               | example you can return a pointer to a stack allocated
               | struct from a foreign lambda (this is because chicken's
               | generated C code here doesn't really "return", I think.
               | Not an expert).
               | 
               | Of course you can always drop to manually written C
               | yourself and it's still a fantastic language to interop
               | with C. And CHICKEN 6 (still pre-release) improves upon
               | that! E.g structs and Unions can be returned/passed
               | directly by/to foreign functions, and the new CRUNCH
               | extension/subset is supposed to compile to something
               | quite a bit closer to handwritten C; there are even
               | people experimenting with it on embedded devices.
        
               | zem wrote:
               | > this is because chicken's generated C code here doesn't
               | really "return", I think. Not an expert.
               | 
               | not an expert either, but you're right about that, it
               | uses cps transformations so that functions never return.
               | there's a nice write up here: https://wiki.call-
               | cc.org/chicken-compilation-process#a-guide...
        
               | jrapdx3 wrote:
               | Chicken indeed interoperates with C quite easily and
               | productively. You're right that the generated C code is
               | mostly incomprehensible to humans, but compiles without
               | difficulty.
               | 
               | The Chicken C API has functions/macros that return values
               | and those that don't return. The former include the
               | fabulous embedded API (crunch is an altogether different
               | beast) which I've used in "mixed language" programming to
               | good effect. In such cases Scheme is rather like the
               | essential "glue" that enables the parts written in other
               | languages to work as a whole.
               | 
               | Of course becoming proficient in Scheme programming takes
               | time and effort. I believe it's true that some brains
               | have an affinity for Lispy languages while others don't.
               | Fortunately, there are many ways to write programs to
               | accomplish a given task.
        
         | echelon wrote:
         | There are going to be lots of languages competing with Rust and
         | Zig. It's a popular, underserved market. They'll all have their
         | unique angle.
        
           | pjmlp wrote:
           | I has been served for several decades, however since the
           | late-90's many decided reducing to only C and C++ was the way
           | going forward, now the world is rediscovering it doesn't have
           | to be like that.
        
           | forgotpwd16 wrote:
           | They're are certainly going to be lots of languages because
           | now with LLMs it's easier (trivial?) to make one + library
           | (case in point: just within last month there're have been
           | posted here ~20 new langs with codebases 20k~100k LOC) but
           | don't really see them competing. Rust and Zig brought actual
           | improvements and are basically replacing usecases that C++/C
           | had limiting the space available to others.
        
         | messe wrote:
         | It has stringly typed macros. It's not comparable to Zig's
         | comptime, even if it calls it comptime:                   fn
         | main() {             comptime {                 var N = 20;
         | var fib: long[20];                 fib[0] = (long)0;
         | fib[1] = (long)1;                 for var i=2; i<N; i+=1 {
         | fib[i] = fib[i-1] + fib[i-2];                 }
         | printf("// Generated Fibonacci Sequence\n");
         | printf("var fibs: int[%d] = [", N);                 for var
         | i=0; i<N; i+=1 {                     printf("%ld", fib[i]);
         | if (i < N-1) printf(", ");                 }
         | printf("];\n");             }                  print "Compile-
         | time generated Fibonacci sequence:\n";             for i in
         | 0..20 {                 print f"fib[{i}] = {fibs[i]}\n";
         | }         }
         | 
         | It just literally outputs characters, not even tokens like
         | rust's macros, into the compiler's view of the current source
         | file. It has no access to type information, as Zig's does, and
         | can't really be used for any sort of reflection as far as I can
         | tell.
         | 
         | The Zig equivalent of the above comptime block just be:
         | const fibs = comptime blk: {             var f: [20]u64 =
         | undefined;             f[0] = 0;             f[1] = 1;
         | for (2..f.len) |i| {                 f[i] = f[i-1] + f[i-2];
         | }             break :blk f;          };
         | 
         | Notice that there's no code generation step, the value is
         | passed seamlessly from compile time to runtime code.
        
         | kuon wrote:
         | I am working on mine as well. I think it is very sane to have
         | some activity in this field. I hope we will have high level
         | easy to write code that is fully optimized with very little
         | effort.
        
         | thomasmg wrote:
         | I wonder, how can a programming language have the productivity
         | of a high-level language ("write like a high-level language"),
         | if it has manual memory management? This just doesn't add up in
         | my view.
         | 
         | I'm writing my own programming language that tries "Write like
         | a high-level language, run like C.", but it does not have
         | manual memory management. It has reference counting with
         | lightweight borrowing for performance sensitive parts:
         | https://github.com/thomasmueller/bau-lang
        
       | alexpadula wrote:
       | 18 commits! I hope you keep up with the project, it's really
       | cool, great work.
        
       | kreco wrote:
       | That's a very nice project.
       | 
       | List of remarks:
       | 
       | > var ints: int[5] = {1, 2, 3, 4, 5};
       | 
       | > var zeros: [int; 5]; // Zero-initialized
       | 
       | The zero initialized array is not intuitive IMO.
       | 
       | > // Bitfields
       | 
       | If it's deterministically packed.
       | 
       | > Tagged unions
       | 
       | Same, is the memory layout deterministic (and optimized)?
       | 
       | > 2 | 3 => print("Two or Three")
       | 
       | Any reason not to use "2 || 3"?
       | 
       | > Traits
       | 
       | What if I want to remove or override the "trait Drawing for
       | Circle" because the original implementation doesn't fit my
       | constraints? As long as traits are not required to be in a
       | totally different module than the struct I will likely never
       | welcome them in a programming language.
        
         | jdmichal wrote:
         | C uses `|` for bitwise OR and `||` for logical OR. I'm assuming
         | this inherited the same operator paradigm since it compiles to
         | C.
        
       | alfonsodev wrote:
       | Is this the Typescript of C ?
        
       | ramses0 wrote:
       | The whole language examples seem pretty rational, and I'm
       | especially pleased / shocked by the `loop / repeat 5` examples. I
       | love the idea of having syntax support for "maximum number of
       | iterations", eg:                   repeat 3 {            try {
       | curl(...) && break }            except { continue }         }
       | 
       | ...obviously not trying to start any holy wars around exceptions
       | (which don't seem supported) or exponential backoff (or
       | whatever), but I guess I'm kindof shocked that I haven't seen any
       | other languages support what seems like an obvious syntax
       | feature.
       | 
       | I guess you could easily emulate it with `for x in range(3):
       | ...break`, but `repeat 3: ...break` feels a bit more like that
       | `print("-"*80)` feature but for loops.
        
         | zeknife wrote:
         | Ruby has a similarly intuitive `3.times do ... end` syntax
        
           | ioasuncvinvaer wrote:
           | go also has                   for range 5 { ... }
        
       | blacksqr wrote:
       | What's the performance hit?
        
       | Lucasoato wrote:
       | > Mutability
       | 
       | > By default, variables are mutable. You can enable Immutable by
       | Default mode using a directive.
       | 
       | > //> immutable-by-default
       | 
       | > var x = 10; > // x = 20; // Error: x is immutable
       | 
       | > var mut y = 10; > y = 20; // OK
       | 
       | Wait, but this means that if I'm reading somebody's code, I won't
       | know if variables are mutable or not unless I read the whole file
       | looking for such directive. Imagine if someone even defined
       | custom directives, that doesn't make it readable.
        
         | andai wrote:
         | Given an option that is configurable, why would the default
         | setting be the one that increases probability of errors?
         | 
         | For some niches the answer is "because the convenience is worth
         | it" (e.g. game jams). But I personally think the error prone
         | option should be opt in for such cases.
         | 
         | Or to be blunt: correctness should not be opt-in. It should be
         | opt-out.
         | 
         | I have considered such a flag for my future language, which I
         | named #explode-randomly-at-runtime ;)
        
           | miroljub wrote:
           | > Or to be blunt: correctness should not be opt-in. It should
           | be opt-out.
           | 
           | One can perfectly fine write correct programs using mutable
           | variables. It's not a security feature, it's a design
           | decision.
           | 
           | That being said, I agree with you that the author should
           | decide if Zen-C should be either mutable or immutable by
           | default, with special syntax for the other case. As it is
           | now, it's confusing when reading code.
        
           | Y_Y wrote:
           | > I have considered such a flag for my future language, which
           | I named #explode-randomly-at-runtime ;)
           | 
           | A classic strategy!
           | 
           | https://p-nand-q.com/programming/languages/java2k/
        
           | psychoslave wrote:
           | But why put it as a global metaswitcher instead of having
           | different type infered from initial assignation qualifier?
           | 
           | Example:                   let integer answer be 42 -- this
           | is a constant         set integer temperature be 37.2 -- this
           | is a mutable
           | 
           | Or with the more esoglyphomaniac fashion
           | cst - 123 // a constant is just a trivial map         st -
           | 29.5 // initial assignment inferring float
        
           | Dylan16807 wrote:
           | > Given an option that is configurable, why would the default
           | setting be the one that increases probability of errors?
           | 
           | They're objecting to the "given", though. They didn't comment
           | either way on what the default should be.
           | 
           | Why should it be configurable? Who benefits from that? If
           | it's to make it so people don't have to type "var mut" then
           | replace that with something shorter!
           | 
           | (Also neither one is more 'correct')
        
         | netbioserror wrote:
         | Yeah, immutability should probably use a `let` keyword and
         | compiler analysis should enforce value semantics on those
         | declarations.
        
           | phcreery wrote:
           | Agreed, using `var` keyword for something that is non-var-
           | ying (aka immutable) is not very intuitive.
        
         | gmueckl wrote:
         | Other languages also have non-local qays of influencing
         | compiler behavior, for example attributes in rust (standard) or
         | compiler pragmas in C (non-standard).
         | 
         | When reading working code, it doesn't matter whether the
         | language mode allows variable reassignment. It only matters
         | when you want to change it. And even then, the compiler will
         | yell at you when you do the wrong thing. Testing it out is
         | probably much faster than searching the codebase for a
         | directive. It doesn't seem like a big deal to me.
        
         | maxbond wrote:
         | It's not ideal but it seems like something an LSP could tell
         | you on a hover event. I didn't see an LSP (I didn't look that
         | hard either) but presumably that's within the scope of their
         | mission statement to deliver modern language ergonomics. (But I
         | agree with sibling comments that this should be a keyword.
         | Another decent alternative would be that it's only global in
         | scope.)
        
       | forgotpwd16 wrote:
       | Basically C2/C3 but Rust influenced. Missed chance to call it C4.
        
       | seabrookmx wrote:
       | It's odd that the async/await syntax _exclusively_ uses threads
       | under the hood. I guess it makes for a straightforward
       | implementation, but in every language I've seen the point of
       | async/await is to use an event loop/cooperative multitasking.
        
         | sestep wrote:
         | Noob question: if it just compiles to threads, is there any
         | need for special syntax in the first place? My understanding
         | was that no language support should be required for blocking on
         | a thread.
        
           | gmueckl wrote:
           | Async/await should do a little more under the hood than what
           | the typical OS threading APIs provide, for example forwarding
           | function parameters and return values automatically instead
           | of making the user write their own boilerplate structs for
           | that.
        
       | Joker_vD wrote:
       | From what I can see in the codegen, defer is not implemented
       | "properly": the deferred statements are only executed when the
       | block exits normally; leaving the block via "return", "break",
       | "continue" (including their labelled variants! those interact
       | subtly with outer defers), or "goto" skips them entirely. Which,
       | arguably, should not happen:                   var f =
       | fopen("file.txt", "r");         defer fclose(f);              if
       | fread(&ch, 1, 1, f) <= 0 { return -1; }         return 0;
       | 
       | would not close file if it was empty. In fact, I am not sure how
       | it works even for normal "return 0": it looks like the deferred
       | statements are emitted after the "return", textually, so they
       | only properly work in void-returning function and internal
       | blocks.
        
         | ricardobeat wrote:
         | Did you manage to compile this example?
        
           | Joker_vD wrote:
           | Yes, actually:                   $ cat kekw.zc
           | include <stdio.h>                  fn main() {
           | var f = fopen("file.txt", "r");             defer fclose(f);
           | var ch: byte;             if fread(&ch, 1, 1, f) <= 0 {
           | return -1; }             return 0;         }         $ ./zc
           | --emit-c kekw.zc         [zc] Compiling kekw.zc...         $
           | tail -n 12 out.c         int main()         {             {
           | __auto_type f = fopen("file.txt", "r");             uint8_t
           | ch;         if ((fread((&ch), 1, 1, f) <= 0))     {
           | return (-1);             }             return 0;
           | fclose(f);             }         }
        
       | stevefolta wrote:
       | Nice! Compiles in 2s on my unexceptional hardware. But it lacks
       | my other main desiderata in a new language: string interpolation
       | and kebab-case.
        
       | taolson wrote:
       | The author includes some easter-eggs (printing random facts about
       | Zen and various C constructs) which trigger randomly -- check out
       | the file src/zen/zen_facts.c in the repository...
        
       | Dwedit wrote:
       | I wonder how this compares to the Beef programming language.
       | 
       | https://www.beeflang.org/
       | 
       | The Beef programming language was used to write Penny's Big
       | Breakaway.
        
       | Zambyte wrote:
       | The tagline also applies to C :-)
        
       | aappleby wrote:
       | Example at the top of the readme!
        
       ___________________________________________________________________
       (page generated 2026-01-12 23:00 UTC)