[HN Gopher] Zig's Lovely Syntax
___________________________________________________________________
Zig's Lovely Syntax
Author : Bogdanp
Score : 184 points
Date : 2025-08-10 15:33 UTC (7 hours ago)
(HTM) web link (matklad.github.io)
(TXT) w3m dump (matklad.github.io)
| do_not_redeem wrote:
| Since we're talking syntax... it's mildly infuriating that the
| zig parser is not smart enough to understand expressions like
| `const x=a()orelse b();`. You have to manually add a space before
| `orelse` -- but isn't that what `zig fmt` is for? I have RSI and
| it's maddening having to mash the arrow keys and add/remove
| whitespace until the parser is happy.
|
| I've heard the argument that people might confuse binary
| operators for prefix/postfix operators, but I don't buy it. Who
| would think an operator named `orelse` is anything but binary?
| hmry wrote:
| "Read the file, or else!" The threatening postfix operator
| nateglims wrote:
| The error when you accidentally pass a variable directly
| instead of in a .{} is also really unclear.
| hiccuphippo wrote:
| No idea why it wouldn't add the space, but you could configure
| your editor to always add a space after `)` and let zig fmt
| remove the space when not needed.
| phplovesong wrote:
| I find Zig syntax noicy. I dont like the @TypeOf (at symbol) and
| pals, and the weird .{.x} syntax feels off.
|
| Zig has some nice things going on but somehow code is really hard
| to read, admitting its a skill issue as im not that versed in
| zig.
| flohofwoe wrote:
| The dot is just a placeholder for an inferred type, and IMHO
| that makes a lot of sense. E.g. you can either write this:
| const p = Point{ .x = 123, .y = 234 };
|
| ...or this: const p: Point = .{ .x = 123, .y
| = 234 };
|
| When calling a function which expects a Point you can omit the
| verbose type: takePoint(.{ .x = 123, .y = 234
| });
|
| In Rust I need to explicitly write the type:
| takePoint(Point{ x: 123, y: 234);
|
| ...and in nested struct initializations the inferred form is
| very handy, e.g. Rust requires you to write this (not sure if I
| got the syntax right): const x = Rect{
| top_left: Point{ x: 123, y: 234 }, bottom_right:
| Point{ x: 456, y: 456 }, };
|
| ...but the compiler already knows that Rect consists of two
| nested Points, so what's the point of requiring the user to
| type that out? So in Zig it's just: const x =
| Rect{ .top_left = .{ .x = 123, .y = 234 },
| .bottom_right = .{ .x = 456, .y = 456 }, };
|
| Requiring the explicit type on everything can get noisy really
| fast in Rust.
|
| Of course the question is whether the leading dot in '.{' could
| be omitted, and personally I would be in favour of that.
| Apparently it simplifies the parser, but such implementation
| details should get in the way of convenience IMHO.
|
| And then there's `.x = 123` vs `x: 123`. The Zig form is copied
| from C99, the Rust form from Javascript. Since I write both a
| lot of C99 and Typescript I don't either form (and both Zig and
| Rust are not even close to the flexibility and convenience of
| the C99 designated initialization syntax unfortunately).
|
| Edit: fixed the Rust struct init syntax.
| do_not_redeem wrote:
| Zig is planning to get rid of explicit `T{}` syntax, in favor
| of only supporting inferred types.
|
| https://github.com/ziglang/zig/issues/5038
|
| So the explanation of a dot standing in for a type doesn't
| make sense in the long run.
| flohofwoe wrote:
| Ah, I wasn't aware of that proposal. But yeah in that case
| I would also heavily prefer to "drop the dot" :)
|
| IMHO Odin got it exactly right. For a variable with
| explicit type: a_variable : type = val;
|
| ...or for inferred type: a_variable :=
| val;
|
| ...and the same for constants: a_const :
| type : val; a_const :: val;
|
| ...but I think that doesn't fit into Zig's parser design
| philosophy (e.g. requiring some sort of keyword upfront so
| that the parser knows the context it's in right from the
| start instead of delaying that decision to a later time).
| dminik wrote:
| Ahh, a perfect example of why Zig is uninteresting to me:
|
| https://github.com/ziglang/zig/issues/5038#issuecomment-244
| 1...
|
| A language hostile to LSP/intellisense.
| hinkley wrote:
| Thanks for the explanation, but I don't think you've sold me
| on .x
|
| Think I'd rather do the Point{} syntax.
| tialaramex wrote:
| Because we've said x is a constant we're obliged to specify
| its type. For variables we're allowed to use inference and in
| most cases the type can be correctly inferred, but for
| constants or function signatures inference is deliberately
| prohibited. const x: Rect = ....
|
| [Note that in Zig what you've written isn't a constant, Zig
| takes the same attitude as C and C++ of using const to
| indicate an immutable rather than a constant]
| dsego wrote:
| Zig is noisy and and the syntax is really not elegant. One
| reason I like odin's syntax, it's minimal and so well thought
| out.
| zabzonk wrote:
| In my experience, everyone finds the syntax of their favourite
| language lovely - I love (mostly) C++.
| jeltz wrote:
| I am an exception then. Rust may be my favourite language but
| the syntax is pretty awful and one of its biggest weaknesses. I
| also love Ruby but I am pretty meh about its syntax.
| zabzonk wrote:
| I don't think many people would start to use a language
| unless the found the syntax at least a little simpatico - but
| I guess they could be drawn by the semantics it provides.
| sampullman wrote:
| I (almost) don't consider syntax at all when considering a
| language, just whether it's suitable for the task.
|
| The exception is languages with syntax that require a non-
| standard keyboard.
| jeltz wrote:
| I mostly chose languages due to semantics.
| ben-schaaf wrote:
| As someone who works almost exclusively in C++, the whole "most
| vexing parse" makes the syntax indefensible.
| zabzonk wrote:
| In my experience, people simply go "Doh, of course!" or don't
| write the wrong code in the first place. It has never caused
| me any real problems.
|
| For those of you that may be curious, or not know C++, in C++
| this: Obj x();
|
| is a declaration of a function called x that returns an Obj.
| Whereas this: Obj x;
|
| defines (possibly, depending on context) an instance of the
| type Obj called x.
|
| Most people get over this pretty quickly.
|
| If that is your main complaint about a language then the
| language doesn't have too many problems. Not that I'm
| suggesting that C++ doesn't have more serious problems.
| aeonik wrote:
| Not me!
|
| I don't like the syntax of Lisps, with the leading parenthesis
| to begin every expression.
|
| I use it anyway because it's so useful and powerful. And I
| don't have any better ideas.
| zabzonk wrote:
| > leading parenthesis to begin every expression
|
| well, that's not really what it is doing - it is saying apply
| this function to these parameters.
| brabel wrote:
| No, it's an s-expression... it may be a function call, but
| it may also be a macro, and inside a macro it may be a
| parameter list or a number of other things.
| guidopallemans wrote:
| I don't like how Python does lambdas, its indentation-based
| blocks, how there's both ' and ", I could go on.
| MrResearcher wrote:
| It's still not clear to me how you can make two comptime closures
| with different contents and pass those as a functor into the same
| function. It needs to have a sort of VTable to invoke the
| function, and yet since the contents are different, the objects
| are different, and their deallocation will be different too.
| Defining VTable in zig seems to be a pretty laborious endeavor,
| with each piece sewn manually.
| zoogeny wrote:
| There was a recent Zig podcast where Andrew Kelley explicitly
| states that manually defining a VTable is their solution to
| runtime polymorphism [1]. In general this means wrapping your
| data in a struct, which is reasonable for almost anything other
| than base value types.
|
| 1. https://youtu.be/x3hOiOcbgeA?si=Kb7SrhdammEiVvDN&t=7620
| pton_xd wrote:
| "Zig doesn't have lambdas"
|
| This surprises me (as a C++ guy). I use lambdas everywhere.
| What's the standard way of say defining a comparator when sorting
| an array in Zig?
| tux1968 wrote:
| Same as C, define a named function, and pass a pointer to the
| sorting function.
| tsimionescu wrote:
| And what if you need to close over some local variable?
| flohofwoe wrote:
| Not possible, you'll need to pass the captured variables
| explicitly into the 'lambda' via some sort of context
| parameter.
|
| And considering the memory management magic that would need
| to be implemented by the compiler for 'painless capture'
| that's probably a good thing (e.g. there would almost
| certainly be a hidden heap allocation required which is a
| big no-no in Zig).
| nine_k wrote:
| ...or escape analysis and lifetimes, but we already have
| Rust %)
| pton_xd wrote:
| As far as I know lambdas in C++ will not heap allocate.
| Basically equivalent to manually defining a struct, with
| all your captures, and then stack allocating it. However
| if you assign a lambda to a std::function, and it's large
| enough, then you may get a heap allocation.
|
| Making all allocations explicit is one thing I do really
| like about Zig.
| andrepd wrote:
| Why would capturing require a heap allocation? Neither
| Rust nor C++ do this.
| flohofwoe wrote:
| You need to store the captured data _somewhere_ if the
| lambda is called after the outer function returns. AFAIK
| C++ (or rather std::function) will heap-allocate if the
| capture size goes above some arbitrary limit (similar to
| small-string optimizations in std::string). Not sure how
| Rust handles this case, probably through some "I can't
| let you do that, Dave" restrictions ;)
| steveklabnik wrote:
| The trick is that "if". Rust won't ever automatically
| heap allocate, but _if_ your closure does get returned to
| a place where the capture would be dangling, it will fail
| to compile. You can then choose to heap allocate it if
| you wish, or do something else instead.
|
| Heap allocating them is fairly rare, because most usages
| are in things like combinators, which have no reason to
| enlarge their scope like that.
| andrepd wrote:
| But that's not a closure, a closure/lambda is not an
| std::function. It's its own type, basically syntactic
| sugar for a struct with the captures vars and operator().
|
| Of course if you want to store it on a type-erased
| container like std::function then you may need to heap
| allocate. Rust's equivalent would be a Box<dyn Fn>.
| flohofwoe wrote:
| Unlike C you can stamp out a specialized and typesafe sort
| function via generics though:
|
| https://ziglang.org/documentation/master/std/#std.sort.binar.
| ..
| tapirl wrote:
| Normal function declarations.
|
| This is indeed a point which makes Zig inflexible.
| lenkite wrote:
| By adopting a syntax like fn add(x: i32,
| i32) i32
|
| they have said perma-goodbye to lambdas. They should have at-
| least considered fn add(x: i32, i32): i32
| hinkley wrote:
| They could still fix it with arrow functions, but it's
| always gonna look weird.
|
| Some other people have tried to explain how they prefer
| types before variable declarations, and they've done a
| decent job of it, but it's the function return type being
| buried that bothers me the most. Since I read method
| signatures far more often than method bodies.
|
| fn i32 add(...) is always going to scan better to me.
| lenkite wrote:
| OK, but with generics return type first tends to becomes
| a monster.
| jmull wrote:
| You can declare an anonymous struct that has a function and
| reference that function inline (if you want).
|
| There's a little more syntax than a dedicated language feature,
| but not a lot more.
|
| What's "missing" in zig that lambda implementations normally
| have is capturing. In zig that's typically accomplished with a
| context parameter, again typically a struct.
| veber-alex wrote:
| So basically, Zig doesn't have lambdas, but because you still
| need lambdas, you need to reinvent the wheel each time you
| need it?
|
| Why don't they just add lambdas?
| flipgimble wrote:
| Because to use lambdas you're asking the language to make
| implicit heap allocations for captured variables. Zig has a
| policy that all allocation and control flow are explicit
| and visible in the code, which you call re-inventing the
| wheel.
|
| Lambdas are great for convenience and productivity.
| Eventually they can lead to memory cycles and leaks. The
| side-effect is that software starts to consume gigabytes of
| memory and many seconds for a task that should take a tiny
| fraction of that. Then developers either call someone who
| understands memory management and profiling, or their
| competition writes a better version of that software that
| is unimaginably faster. ex. https://filepilot.tech/
| do_not_redeem wrote:
| Nothing about lambdas requires heap allocation. See also:
| C++, Rust
| porridgeraisin wrote:
| If the lambda captures some value, and also outlives the
| current scope, then that captured value has to
| necessarily be heap allocated.
| do_not_redeem wrote:
| That would be a bug, so just... don't do that?
|
| If you return a pointer to a local variable that outlives
| the scope, the pointer would be dangling. Does that mean
| we should ban pointers?
|
| If you close over a pointer to a local variable that
| outlives the scope, the closure would be dangling. Does
| that mean we should ban closures?
| noselasd wrote:
| No, (in C++) the lambda can capture the the variable by
| value, and the lambda itself can be passed around by
| value. If you capture a variable by reference or pointer
| that your lambda outlives, your code got a serious bug.
| gf000 wrote:
| And in Rust, it will enforce correct usage via the borrow
| checker - the outlive case simply will not compile.
|
| If you do want it, you have the option to, say, heap
| allocate.
| jmull wrote:
| > So basically...
|
| Well, not really.
|
| Consider lambdas in C++ (that was the perspective of the
| post I replied to). Before lambdas, you used functors to do
| the same thing. However, the syntax was slightly cumbersome
| and C++ has the design philosophy to add specialized
| features to optimize specialized cases, so they added
| lambdas, essentially as syntactic sugar over functors.
|
| In zig the syntax to use an anonymous struct like a functor
| and/or lambda is pretty simple and the language has the
| philosophy to keep the language small.
|
| Thus, no need for lambdas. There's no re-inventing
| anything, just using the language as it designed to be
| used.
| hardwaregeek wrote:
| Everyone agrees that "syntax doesn't matter", but implicit in
| that is "syntax doesn't matter, so let's do what I prefer". So
| really, syntax does matter. Personally I prefer the Rust/Zig/Go
| syntax of vaguely C inspired with some nice fixes, as detailed in
| the post. Judging by the general success of that style, I do
| wonder if more functional languages should consider an
| alternative syntax in that style. The Haskell/OCaml concatenative
| currying style with whitespace is elegant, but sufficiently
| unfamiliar that I do think it hurts adoption.
|
| After all, Rust's big success is hiding the spinach of functional
| programming in the brownie of a systems programming language. Why
| not imitate that?
| nromiun wrote:
| That saying never made any sense to me either. After all syntax
| is your main interface to a language. Anything you do has to go
| through the syntax.
|
| Some people say the syntax just kind of disappears for them
| after some time. That never seems to happen with me. When I am
| reading any code the syntax gets even more highlighted.
| brabel wrote:
| I always thought that languages (or at least editors) should
| allow the user to use whatever syntax they want! It's
| possible. Just look at Kotlin and Java... you can write the
| exact same code in both, even if you discount the actual
| differences in the languages. It's not hard to see how you
| could use Haskell, or Python syntax, to write the exact same
| thing. People don't like the idea too much because they think
| that makes it sharing code and reading code together harder,
| but I don't buy that because at least I myself, read code by
| myself 99% of the time, and the few times I read code with
| someone else, I can imagine we can just agree quite easily on
| which syntax to use - or just agree to always use the
| "default" syntax to avoid disagreements.
|
| I dream to one day get the time to write an editor plugin
| that lets you read and write in any syntax... then commit the
| code in the "standard" syntax. How hard would that be?!
| hinkley wrote:
| People who do a lot of deep work on code, particularly
| debugging and rearchitecting, tend to have opinions about
| syntax and code style that are more exacting. As one of those
| people I've always felt that the people working the code the
| hardest deserve to have an outsized vote on how to organize
| the code.
|
| Would that potentially reduce throughput a bit? Probably. But
| here's the thing: most management notice how the bad
| situations go more than the happiest path. They will kick you
| when you're down. So tuning the project a bit for the
| stressful days is like Safety. Sure it would be great to put
| a shelf here but that's where the fire extinguishers need to
| go. And sure it would be great not interrupting the workday
| for fire safety drills, but you won't be around to complain
| about it if we don't have them. It's one of those
| counterintuitive things, like mise en place is for a lot of
| people. Discipline is a bit more work now for less stress
| later.
|
| You get more _predictable_ throughput from a system by making
| the fastest things a little slower to make the slow things a
| lot faster. And management needs predictability.
| tmtvl wrote:
| One of the reasons why I use Lisp is because the syntax helps
| me keep my mind organised. C-style syntax is just too chaotic
| for me to handle.
| brabel wrote:
| If you like C-like syntax and want a functional language that
| uses it, try Gleam: https://gleam.run/
|
| Quite lovely looking code. fn
| spawn_greeter(i: Int) { process.spawn(fn() {
| let n = int.to_string(i) io.println("Hello from "
| <> n) }) }
|
| There's also Reason, which is basically OCaml (also compiles to
| JS - funnily enough, Gleam does that too.. but the default is
| the Erlang VM) with C-like syntax: https://reasonml.github.io/
| bscphil wrote:
| > Like Rust, Zig uses 'name' (':' Type)? syntax for ascribing
| types, which is better than Type 'name'
|
| I'm definitely an outlier on this given the direction all
| syntactically C-like new languages have taken, but I have the
| opposite preference. I find that the most common reason I go back
| to check a variable declaration is to determine the type of the
| variable, and the harder it is to visually find that, the more
| annoyed I'm going to be. In particular, with statically typed
| languages, my mental model tends to be "this is an int" rather
| than "this is a variable that happens to have the type 'int'".
|
| In Rust, in particular, this leads to some awkward syntactic
| verbosity, because mutable variables are declared with `let mut`,
| meaning that `let` is used in every declaration. In C or C++ the
| type would take the place of that unnecessary `let`. And even C
| (as of C23) will do type inference with the `auto` keyword. My
| tendency is to use optional type inference in places where
| needing to know the type isn't important to understand the code,
| and to specify the type when it would serve as helpful commentary
| when reading it back.
| SkiFire13 wrote:
| > In C or C++ the type would take the place of that unnecessary
| `let`
|
| In my opinion the `let` is not so unnecessary. It clearly marks
| a statement that declares a variable, as opposed to other kind
| of statements, for example a function call.
|
| This is also why C++ need the "most vexing parse" ambiguity
| resolution.
| reactordev wrote:
| I'm in the same boat. It's faster mentally to grok the type of
| something when it comes first. The name of the thing is less
| important (but still important!) than the type of the thing and
| so I prefer types to come before names.
|
| From a parser perspective, it's easier to go name first so you
| can add it to the AST and pass it off to the type determiner to
| finish the declaration. So I get it. In typescript I believe
| it's this way so parsers can just drop types all together to
| make it compatible with JavaScript (it's still trivial to strip
| typing, though why would you?) without transpiling.
|
| In go, well, you have even more crazier conventions. Uppercase
| vs lowercase public vs private, no inheritance, a gc that shuns
| away performance minded devs.
|
| In the end, I just want a working std library that's easy to
| use so I can build applications. I don't care for:
| type Add<A extends number, B extends number> = [
| ...Array<A>, ...Array<B>, ]["length"]
|
| This is the kind of abuse of the type system that drives me
| bonkers. You don't have to be clever, just export a function. I
| don't need a type to represent every state, I need _intent_.
| erk__ wrote:
| Maybe it just have to do with what you are used to, it was one
| of the things that made me like Rust coming from F# it had the
| same `name : type` and `let mutable name` that I knew from
| there.
| Quekid5 wrote:
| > I find that the most common reason I go back to check a
| variable declaration is to determine the type of the variable,
|
| Hover the mouse cursor over it. Any reasonable editor will show
| the type.
|
| > In Rust, in particular, this leads to some awkward syntactic
| verbosity, because mutable variables are declared with `let
| mut`, meaning that `let` is used in every declaration.
|
| Rust is very verbose for strange implementation reasons...
| namely to avoid parse ambiguities.
|
| > In C or C++ the type would take the place of that unnecessary
| `let`.
|
| OTOH, that means you can't reliably grep for declarations of a
| variable/function called "foo". Also consider why some people
| like using auto foo(int blah) -> bool
|
| style. This was introduced because of template nonsense (how to
| declare a return type before the type parameters were known),
| but it makes a lot of sense and makes code more greppable.
| Generic type parameters make putting the return type at the
| front very weird -- reading order wise.
|
| Anyhoo...
| AnimalMuppet wrote:
| I, too, go back up the code to find the type of a variable. But
| I see the opposite problem: if the type is first, then it
| becomes harder to find the line that declares the variable I'm
| interested in, because the variable name isn't first. It's
| after the type, and the type could be "int" or it could be
| "struct Frobnosticator". That is, the variable name is after a
| variable-length type, so I have to keep bouncing left to right
| to find the start of the variable name.
| MoltenMan wrote:
| I much prefer Pascal typing because it
|
| 1. Allows type inference without a hacky 'auto' workaround like
| c++ and 2. Is less ambiguous parsing wise. I.e. when you read
| 'MyClass x', MyClass could be a variable (in which case this is
| an error) or a type; it's impossible to know without context!
| Y_Y wrote:
| > C uses a needlessly confusing spiral rule
|
| Libellous! The "spiral rule" for C is an abomination and not part
| of the language. I happen to find C's type syntax a bit too
| clever for beginners, but it's marvellously consistent and
| straightforward. I can read types fine without that spiral
| nonsense, even if a couple of judicious typedefs are generally a
| good idea for any fancy function types.
| throwawaymaths wrote:
| It's not context free though.
| Rusky wrote:
| It is context free, just ambiguous.
| Twey wrote:
| > fancy function types
|
| If the syntax were straightforward, plain old function types
| wouldn't be 'fancy' :)
| Y_Y wrote:
| Ambiguous parse. I don't mean that all function types are
| fancy, I mean that some are fancy (when you have a function
| pointers in the arguments or return value, or several layers
| of indirection), and that those which are fancy are helped by
| typedefs.
| Twey wrote:
| I maintain -- they shouldn't be fancy. 'Fancy' is the mark
| of something your language doesn't support well. They can
| be _long_, and that's often worth breaking up, but if
| something is 'fancy' it's because it's not clear to read,
| so you should attach some identifier to it that explains
| what it's supposed to mean. That's significantly, though
| not exclusively, a function (ha) of the syntax used to
| express the concept.
|
| If you're working with Roman numerals, division is a very
| fancy thing to do.
| Y_Y wrote:
| Fair enough. I think it qualifies as "essential
| complexity" and in my limited experience it's not a
| common use case and so doesn't make sense to optimize
| for.
|
| In fact in my academic and professional career most of
| the highly "functional" C that I've come across has been
| written by me, when I'd rather amuse myself than make
| something readable.
|
| [0] https://en.wikipedia.org/wiki/Essential_complexity
| pjmlp wrote:
| Having @ and .{ } all over the place is hardly lovely, as is
| having modules like JavaScript's CJS.
| Akronymus wrote:
| It's especially bad on a qwertz keyboard as well.
| flohofwoe wrote:
| That's why "real programmers" use English keyboard layout
| regardless of the physical keyboard ;)
|
| Curly brace syntax would never have been invented in Europe
| (case in point: Python and Pascal).
| Akronymus wrote:
| Oh for sure. I am using qwerty myself. And my fav language
| (f#) has relatively few curly braces.
| christophilus wrote:
| F# is wonderful. I wish someone would make an F# that
| compiled as fast as OCaml or Go and which had Go's
| standard library and simple tooling.
| pjmlp wrote:
| .NET ecosystem is only matched by Java, and Native AOT
| exists, even if there are some issues with printf, due to
| its current implementation.
| pjmlp wrote:
| Real programmers deliver business value, regardless of what
| keyboard they have at their disposal.
|
| Just like great musicians make the difference in the band,
| regardless of the instruments scattered around the studio.
| lenkite wrote:
| JSON is everywhere nowadays - How can one complain about
| curly braces {} ? EU programmers would have already mapped
| {} after their first REST API.
| hinkley wrote:
| I've been using Dvorak for 28 years and I still fat finger
| punctuation daily.
| fallow64 wrote:
| Every file is an implicit struct, so importing a module is just
| importing a struct with static members.
|
| You can also do something like:
|
| ```Point.zig x: i32, y: i32,
|
| const Self = @This(); fn add(self: _Self, other:_ Self) Self {
| // ... } ```
| pjmlp wrote:
| Yeah, which is basically how _requires()_ kind of works.
| the__alchemist wrote:
| I wish Zig had lovely vector, quaternion, matrix etx syntax. The
| team's refusal to add operator overloading will prevent this.
| flohofwoe wrote:
| You don't need operator overloading for vector and matrix math,
| see pretty much all GPU languages. What Zig is missing is a
| complete mapping of the Clang Extended Vector and Matrix
| extensions (instead of the quite limited `@Vector` type):
|
| https://clang.llvm.org/docs/LanguageExtensions.html#vectors-...
|
| https://clang.llvm.org/docs/LanguageExtensions.html#matrix-t...
| renox wrote:
| Agreed, their reason for not allowing it is weird. No hidden
| overloading? OK make it explicit then: #+, #/ would be fine.
| Cyph0n wrote:
| That would open a can of worms, because then the next thing
| would use a different symbol. AFAIK, Scala had a huge issue
| with random symbols polluting code readability.
| nromiun wrote:
| I like Zig as well, but I won't call its syntax lovely. Go shows
| you can do pretty well without ; for line breaks, without : for
| variable types etc.
|
| But sure, if you only compare it with Rust, it is a big
| improvement.
| nine_k wrote:
| I personally find Go's bare syntax harder to parse when
| reading, and I spend more time reading code than typing it
| (even while writing).
|
| An excessively terse syntax becomes very unforgiving, when a
| typo is not noticed by the compiler / language server, but
| results in another syntactically correct but unexpected
| program, or registers as a cryptic error much farther
| downstream. Cases in point: CoffeeScript, J.
| nromiun wrote:
| That is why syntax debates are so difficult. There is no
| objectively best syntax. So we are all stuck with subjective
| experience. For me I find Python (non-typed) and Golang
| syntax easiest to read.
|
| Too many symbols like ., :, @, ; etc just mess with my brain.
| aatd86 wrote:
| Yes it's sigils that are the culprits more often than not.
| They are often semantically irrelevant and just make things
| easier to parse for the machines. Happy Go doesn't indulge
| too much in them.
| IshKebab wrote:
| Removing stuff doesn't necessarily make the syntax better,
| otherwise we'd all use Lisp and the space bar wouldn't exist:
| https://en.wikipedia.org/wiki/Scriptio_continua
| nromiun wrote:
| True. But my point is that adding stuff doesn't necessarily
| make the syntax better either. Otherwise we would all be
| using Perl by now. The sweet spot is somewhere in the middle.
| Western0 wrote:
| Love Ruby and Cristal syntax ;-)
| darthrupert wrote:
| Absolutely. Kinda of the best of many worlds, because it has
| what looks like an indentation-based syntax but actually blocks
| are delimited by start and end keywords. This makes it possible
| (unlike, say, python) to programmatically derive correct code
| formatting more reliably.
|
| And no pointless semicolons.
| WalterBright wrote:
| const x: i32 = 92;
|
| D has less syntax: const int x = 92;
|
| Just for fun, read each declaration out loud.
| WalterBright wrote:
| Zig: const std = @import("std");
|
| D: import std;
| brabel wrote:
| I think this is not equivalent because in D, this imports all
| symbols in the package `std` while in Zig, you just get a
| "struct" called `std`. I think the equivalent D is:
| import std=std;
| WalterBright wrote:
| Zig: fn ArrayListType(comptime T: type) type
| {
|
| D: T ArrayListType(T)() {
| dpassens wrote:
| I don't know D but shouldn't that be struct
| ArrayListType(T) { ?
| brabel wrote:
| Walter is showing the equivalent function declaration...
| you will eventually create a generic type as you say, but
| in the Zig example, that was a function, not a struct.
| dpassens wrote:
| If my understanding of D's template syntax is correct,
| then Walter is showing a the declaration of a function
| called ArrayListType which is generic over T and returns
| a T. The original Zig code returns the struct type
| itself, so it is functionally equivalent to my example,
| provided I understood how D templates work.
| brabel wrote:
| The Zig code returns any `type`, it's impossible to say
| what that is without looking at the implementation. It
| can be different types completely depending on the
| comptime arguments.
|
| But I agree it probably returns a struct type.
|
| Assuming that's the case, you're right and the equivalent
| would be:
|
| Zig: fn ArrayListType(comptime T: type)
| type {
|
| D: ArrayList!T ArrayListType(T)() {
|
| But now the D version is more specific about what it
| returns, so it's still not exactly equivalent.
| dpassens wrote:
| No, you misunderstand. The function doesn't return any
| type, it returns _a_ type. Types are values in Zig and
| returning them from function is how generics are
| implemented.
| do_not_redeem wrote:
| Why is omitting the fact that T is a type useful? T could be
| a normal value too.
|
| This reminds me of C in the 1970s where the compiler assumed
| every typo was a new variable of type int. Explicit is good.
| WalterBright wrote:
| > Why is omitting the fact that T is a type useful?
|
| It's the default, because most templates are templated on
| types. If you want a constant int as part of the template
| type, ArrayListType(int I)
|
| > This reminds me of C in the 1970s where the compiler
| assumed every typo was a new variable of type int
|
| I think you're referring to function declarations without
| prototypes. D's syntax does not suffer from that issue.
|
| BTW, T func(T)(T x) { return x + 1; }
|
| is a declaration of a "function template". The first
| parameter list consists of the template parameters, and are
| compile time types and constants. The second parameter list
| is the conventional function parameter list. If the first
| parameter list is omitted, then it's a function.
| IshKebab wrote:
| "less syntax" isn't necessarily better. The goal isn't to have
| as few characters as possible.
| WalterBright wrote:
| I agree. I'm not a fan of minimized syntax (see Haskell). But
| I am a fan of easy to read syntax!
| johnisgood wrote:
| True, it is subjective. I prefer C, Go, PHP, ... and OCaml,
| Erlang, Elixir, Perl, and sometimes Ada and Common Lisp.
|
| I like the syntaxes of each, although Ada is too verbose to
| me, and with Factor and Common Lisp I have a skill issue.
| tmtvl wrote:
| If it were we would all be using APL.
| lvl155 wrote:
| Zig is just fun to write. And to me, it's actually what I wish
| Rust was like. Rust is a great language, and no one's going to
| argue that point but writing Zig for the first time was so
| refreshing. That said, Rust is now basically default for systems
| and Zig came too late.
| throwawaymaths wrote:
| never underestimate second mover advantage. if zig gets static
| borrow checking, it would be amazing
| gf000 wrote:
| I really doubt a borrow checker could fit with zig's design
| goals, and you can't just add it after the fact.
| Twey wrote:
| > I think Kotlin nails it: val, var, fun. Note all three are
| monosyllable, unlike const and fn!
|
| At least in my pronunciation, all five of those are monosyllabic!
| (/kQnst/, /f@n/).
|
| (Nice to see someone agree with me on minimizing syllable count,
| though -- I definitely find it easier to chunk[1] fewer
| syllables.)
|
| [1]: https://en.m.wikipedia.org/wiki/Chunking_(psychology)
|
| The use of Ruby block parameters for loops in a language without
| first-class blocks/lambdas is a bit weird to me. I might have
| preferred a syntax that looks more like normal variable binding.
| losvedir wrote:
| > Note all three are monosyllable, unlike const and fn!
|
| I'm not sure if I'm parsing this right, but is the implication
| that "const" is not monosyllabic? It certainly is for me. How
| else do people say it? I get "fn" because people might say "eff
| enn" but I don't see what the equivalent for const would be.
| z_open wrote:
| > Raw or multiline strings are spelled like this:
| const still_raw = \\const raw = \\
| \\Roses are red \\ \\ Violets are blue,
| \\ \\Sugar is sweet \\ \\ And so are you.
| \\ \\ \\; \\ ;
|
| This syntax seems fairly insane to me.
| fcoury wrote:
| I really like zig but that syntax is indeed insane.
| IshKebab wrote:
| Maybe if you've never tried formatting a traditional multiline
| string (e.g. in Python, C++ or Rust) before.
|
| If it isn't obvious, the problem is that you can't indent them
| properly because the indentation becomes part of the string
| itself.
|
| Some languages have magical "removed the indent" modes for
| strings (e.g. YAML) but they generally suck and just add
| confusion. This syntax is quite clear (at least with respect to
| indentation; not sure about the trailing newline - where does
| the string end exactly?).
| z_open wrote:
| Even if we ignore solutions other languages have come up
| with, it's even worse that they landed on // for the syntax
| given that it's apparently used the same way for real
| comments.
| n42 wrote:
| > it's even worse that they landed on // for the syntax
|
| .. it is using \\\
| hinkley wrote:
| I worked with browsers since before most people knew what
| a browser was and it will never cease to amaze me how
| often people confuse slash and backslash, / and \
|
| It's some sort of mental glitch that a number of people
| fall into and I have absolutely no idea why.
| Twey wrote:
| I wonder if it's dyslexia-adjacent. Dyslexic people
| famously have particular difficulty distinguishing
| rotated and reflected letterforms.
| hinkley wrote:
| Could be. The frequency is such that it could be
| dyslexics. It's not all the time, but it's a steady rate
| of incidence.
| quesera wrote:
| I think in the 90's it was just people repeating a
| pattern they learned from Windows/DOS.
|
| It used to grate on my nerves to hear people say, e.g. "H
| T T P colon backslash backslash yahoo dot com".
|
| But I think they always typed forward slash, like they
| knew the correct slash to use based on the context, but
| somehow always spoke it in DOSish.
| Mawr wrote:
| I doubt those very people would confuse the two when
| presented with both next to each other: / \ / \\. The
| issue is, they're not characters used day-to-day so few
| people have made the association that the slash is the
| one going this way / and not the one going the other way
| \\. They may not even be aware that both exist, and just
| pick the first slash-like symbol they see on their
| keyboards without looking further.
| IshKebab wrote:
| Yeah I agree \\\ is not the best choice visually (and
| because it looks quite similar to //
|
| I would have probably gone with ` or something.
| WCSTombs wrote:
| But those are two different slashes? \\\ for strings and //
| for comments?
| Blackarea wrote:
| We can just use a crate for that and don't have to have this
| horrible comment like style that brings its own category of
| problems. https://docs.rs/indoc/latest/indoc/
| Twey wrote:
| And what if you do want to include two spaces at the
| beginning of the block (but not any of the rest of the
| indentation)?
|
| Choice of specific line-start marker aside, I think this is
| the best solution to the indented-string problem I've seen
| so far.
| gf000 wrote:
| I think Java's solution is much cleaner.
| IshKebab wrote:
| For those of us that haven't used Java for a decade...
|
| > In text blocks, the leftmost non-whitespace character
| on any of the lines or the leftmost closing delimiter
| defines where meaningful white space begins.
|
| From https://blogs.oracle.com/javamagazine/post/text-
| blocks-come-...
|
| It's not a bad option but it does mean you can't have
| text where every line is indented. This isn't uncommon -
| e.g. think about code generation of a function body.
| konart wrote:
| I may be missing something but come Go has a simple:
| `A simple formatted
| string ` ?
| rybosome wrote:
| Yours is rendered as:
|
| A\n\tsimple\n\t\tformatted\n\t\t\tstring\n\t
|
| If you wanted it without the additional indentation, you'd
| need to use a function to strip that out. Typescript has
| dedent which goes in front of the template string, for
| example. I guess in Zig that's not necessary which is nice.
| bmacho wrote:
| Maybe Go doesn't get right the whitespaces so it is a bad
| example, but they are possible to get right. Zig values
| the ability to parse the code parallel more than the
| loveliness of the syntax (or the readability of the code)
| and it shows.
| xigoi wrote:
| The problem is that usually you have something like
| fn main() { if something { print(`A
| simple formatted string`) } }
|
| which looks ugly and confusing.
| norir wrote:
| Significant whitespace is not difficult to add to a language
| and, for me, is vastly superior than what zig does both for
| strings and the unnecessary semicolon that zig imposes by
| _not_ using significant whitespace.
|
| I would so much rather read and write: let
| x = """ a multiline string
| example """
|
| than let x = //a
| //multiline string //example ;
|
| In this particular example, zig doesn't look that bad, but
| for longer strings, I find adding the // prefix onerous and
| makes moving strings around different contexts needlessly
| painful. Yes, I can automatically add them with vim commands,
| but I would just rather not have them at all. The trailing
| """ is also unnecessary in this case, but it is nice to have
| clear bookends. Zig by contrast lacks an opening bracket but
| requires a closing bracket, but the bracket it uses `;` is
| ambiguous in the language. If all I can see is the last line,
| I cannot tell that a string precedes it, whereas in my
| example, you can.
|
| Here is a simple way to implement the former case: require
| tabs for indentation. Parse with recursive descent where the
| signature is (source: string, index:
| number, indent: number, env: comp_env) => ast
|
| Multiline string parsing becomes a matter of bumping the
| indent parameter. Whenever the parser encounters a newline
| character, it checks the indentation and either skips it, or
| if is less than the current indentation requires a closing
| """ on the next line at a reduced indentation of one line.
|
| This can be implemented in under 200 lines of pure lua with
| no standard library functions except string.byte and
| string.sub.
|
| It is common to hear complaints about languages that have
| syntactically significant whitespace. I think a lot of the
| complaints are fair when the language does not have strict
| formatting rules: python and scala come to mind as examples
| that do badly with this. With scala, practically everyone
| ends up using scalafmt which slows down their build
| considerably because the language is way too permissive in
| what it allows. Yaml is another great example of significant
| whitespace done poorly because it is too permissive. When
| done strictly, I find that a language with significant
| whitespace will always be more compact and thus, in my
| opinion, more readable than one that does not use it.
|
| I would never use zig directly because I do not like its
| syntax even if many people do. If I was mandated to use it, I
| would spend an afternoon writing a transpiler that would
| probably be 2-10x faster than the zig compiler for the same
| program so the overhead of avoiding their decisions I
| disagree with are negligible.
|
| Of course from this perspective, zig offers me no value.
| There is nothing I can do with zig that I can't do with c so
| I'd prefer it as a target language. Most code does not need
| to be optimized, but for the small amount that does,
| transpiling to c gives me access to almost everything I need
| in llvm. If there is something I can't get from c out of llvm
| (which seems highly unlikely), I can transpile to llvm
| instead.
| winwang wrote:
| Does `scalafmt` really slow down builds "considerably"? I
| find that difficult to believe, relative to compile time.
| hardwaregeek wrote:
| My immediate thought was hmm, that's weird but pretty nice. The
| indentation problem indeed sucks and with a halfway decent
| syntax highlighter you can probably de-emphasize the `//` and
| make it less visually cluttered.
| n42 wrote:
| Zig does not really try to appeal to window shoppers. this is
| one of those controversial decisions that, once you become
| comfortable with the language by using it, you learn to
| appreciate.
|
| spoken as someone who found the syntax offensive when I first
| learned it.
| throw10920 wrote:
| It seems very reasonable and comes with several technical and
| cognitive advantages. I think you're just having a knee-jerk
| emotional reaction because it's different than what you're used
| to, not because it's actually bad.
| steveklabnik wrote:
| I had the exact opposite reaction.
| conorbergin wrote:
| I think everyone has this reaction until they start using it,
| then it makes perfect sense, especially when using editors that
| have multiple cursors and can operate on selections.
| klas_segeljakt wrote:
| When I first read it I thought it was line comments.
| watersb wrote:
| Upvoting because similar comments here suggest that you are
| not alone.
|
| People are having trouble distinguishing between '//' and
| '\\\'.
| rybosome wrote:
| Visually I dislike the \\\, but I see this solves the problem
| of multiline literals and indentation in a handy, unambiguous
| way. I'm not actually aware of any other language which solves
| this problem without a function.
| whitehexagon wrote:
| I think Kotlin solves it quite nicely with the trimIndent. I
| seem to recall Golang was my fav, and Java my least, although I
| think Java also finally added support for a clean text block.
|
| Makes cut 'n' paste embedded shader code, assembly, javascript
| so much easier to add, and more readable imo. For something
| like a regular expressions I really liked Golang's back tick
| 'raw string' syntax.
|
| In Zig I find myself doing an @embedFile to avoid the '\\\'
| pollution.
| ivanjermakov wrote:
| It is not the insane syntax, but quite insane problem to solve.
|
| Usually, representing multiline strings within another
| multiline string requires lots of non-trivial escaping. This is
| what this example is about: no escaping and no indent nursery
| needed in Zig.
| qcnguy wrote:
| Weird the author finds it lovely and compares to Kotlin, but
| doesn't find Kotlin superior. Kotlin invested heavily in a really
| nice curly brace syntax. It is actually the nicest out there. In
| every point the author makes, it feels like Kotlin did it the
| same or better. For example:
|
| 1. Integer literals. "var a = 1" doesn't work, seems absurd. In
| Kotlin literals do have strong types, but coercion is allowed
| when defining variables so "var a = 1" works, and "var a: Long =
| 1" works even though you can write a literal long as 1L. This
| means you can write numbers to function parameters naturally.
|
| 2. Multi-line string literals. OK this is a neat idea, but what
| about copy/paste? In Kotlin you can just write """ ..
| """.trimIndent() and then copy paste some arbitrary text into the
| string, the indent will be removed for you. The IDE will also
| help with this by adding | characters which looks more natural
| than \\\ and can be removed using .trimMargin(), only downside is
| the trimming is done at runtime but that could easily be fixed
| without changing the language.
|
| 3. Record literals. This syntax is called lovely because it's
| designed for grep, a properly funded language like Kotlin just
| uses named kwargs to constructors which is more natural. There's
| no need to design the syntax for grep because Kotlin is intended
| to be used with a good IDE that can answer this query instantly
| and precisely.
|
| 4. Function syntax. "fn foo(a: i32) i32 {}" seems weird. If the
| thing that has a type and the type are normally separated by a :
| then why not here? Kotlin does "fun foo(a: Int): Int {}" which is
| more consistent.
|
| 5. Locals. Agree with author that Kotlin nails it.
|
| 6. Not using && or ||, ok this one Zig wins, the Zig way is more
| consistent and reads better. Kotlin does have `and` and `or` as
| infix operator functions, but they are for the bitwise operations
| :(
|
| 7. Explicit returns. Kotlin supports blocks that return values
| and also doesn't need semicolons, so not quite sure what the
| tradeoff here is supposed to be about.
|
| 8. Loops being expressions is kinda cool but the Kotlin
| equivalent of his example is much easier to read still: "val
| thing = collection.first { it.foo > bar }". It compiles to a for
| loop due to the function inlining.
|
| 9. Generics. Zig's way seems primitive and unnecessarily complex.
| In Kotlin it is common to let the compiler infer generics based
| on all available information, so you can just write
| "someMethod(emptyList())" and emptyList<T>() infers to the
| correct type based on what someMethod expects.
|
| Overall Zig looks like a lot of modern languages, where they are
| started as a hobby or side project of some guy and so the
| language is designed around whatever makes implementing the
| compiler most convenient. Kotlin is unusual because it doesn't do
| that. It was well funded from the start, so the syntax is
| designed first and foremost to be as English-like and convenient
| as possible without leaving the basic realm of ordinary curly-
| brace functions-and-oop style languages. It manages to be highly
| expressive and convenient without the syntax feeling overly
| complex or hard to learn.
| johnisgood wrote:
| It is not just syntax that matters. For one I dislike JVM-based
| languages. I still write it sometimes for work.
| qcnguy wrote:
| There is also Kotlin/Native, but yes, I am only talking about
| syntax here.
| lenkite wrote:
| > There's no need to design the syntax for grep because Kotlin
| is intended to be used with a good IDE that can answer this
| query instantly and precisely.
|
| On large projects its still cheaper and faster to grep from the
| CLI than to use Intellij IDE search. Esp if you wish to
| restrict search to subsets of dirs.
|
| Command-line greppability is a serious use case.
|
| Zig is also 5-10x faster to compile than Kotlin.
|
| On function syntax, I agree with you. It was a mistake not to
| use fn:ReturnType. It has destroyed future lambdas.
| brabel wrote:
| > On large projects its still cheaper and faster to grep from
| the CLI than to use Intellij IDE search. Esp if you wish to
| restrict search to subsets of dirs.
|
| You must never have used Intellij to say that... it hurts me
| to hear this. If I catch a developer "grepping" for some type
| in the CLI, I will sit down with them for a few hours
| explaining how to use an IDE and how grep is just dumb text
| search without any of the semantic understanding of the code
| that an IDE has, and they should never do that again.
|
| EDIT: IntelliJ is better than grep even at "free text"
| search... Much better as it will show you the results as you
| type, extremely fast, and lets you preview them and see their
| "surrounding code"... and you can then choose to navigate to
| a place instantly... and yes, you can scope the search to a
| particular directory if you want... if you can't see how this
| is miles superior to CLI grep, then there's no use arguing as
| you've made up your mind you just love being in the CLI for
| no actual rational reason.
| lenkite wrote:
| Umm..I _am_ talking about the free text search. Miles
| superior is NOT miles faster when you want speed. You need
| not change tabs, fiddle laboriously with the finicky scope
| drop-down and create custom scopes. Instead you execute an
| ripgrep command with filter directories (or load alias/from
| history), pipe to neovim and get auto-preview of results,
| including surrounding preview code. You don't have to load
| a huge project and wait for looong code-indexing.
|
| if you can't see how this is miles superior to IDE grep
| (esp when exploring a number of large projects), then
| there's no use arguing as you've made up your mind you just
| love being in the IDE for no actual rational reason.
| brabel wrote:
| > Instead you execute an ripgrep command with filter
| directories (or load alias/from history), pipe to neovim
|
| Talk about moving the goal posts.
| lenkite wrote:
| > Talk about moving the goal posts.
|
| Hey, you are the one who moved the goal posts fist by
| bringing up use-cases like surrounding preview. You can
| use ripgrep -> fzf -> bat in one single command if you
| want to stick to the CLI for fast search previews. But I
| personally like neovim's comfort better and it is
| extremely fast to load compared to Intellij. (Not to
| mention, you can keep feeding the quickfix list more
| search results from other places)
| johnisgood wrote:
| He said cheaper and faster. It takes 5-10 minutes for
| IntelliJ to start up properly for me, and doing anything in
| it is just too slow. (rip)grep is way faster.
|
| Yes yes, I need a better PC. (rip)grep would still be
| faster, however, but I would use the IDE.
| IshKebab wrote:
| > As Zig has only line-comments
|
| Such a good decision. There's no reason to use block comments in
| 2025.
| kcartlidge wrote:
| I much prefer C# 11's raw string literals. It takes the
| indentation of the first line and assumes the subsequent ones
| have the same indentation. string json = $"""
| <h1>{title}</h1> <article> Welcome to
| {sitename}. </article> """;
|
| And it even allows for using embedded curly braces as real
| characters: string json = $$"""
| <h1>{{title}}</h1> <article> Welcome to
| {{sitename}}, which uses the <code>{sitename}</code> syntax.
| </article> """;
|
| The $ (meaning to interpolate curly braces) appears twice, which
| switches interpolation to two curly braces, leaving the single
| ones untouched.
| Metasyntactic wrote:
| Just a minor correction (as I'm the author of c#'s raw string
| literal feature).
|
| The indentation of the final ` """` line is what is removed
| from all other lines. Not the indentation of the first line.
| This allows the first line to be indented as well.
|
| Cheers, and I'm glad you like it. I thought we did a really
| good job with that feature :-)
| gf000 wrote:
| Really not trying to go into any of the "holy wars" here, but
| could you please compare C#'s feature to Java's multi-line
| strings? I'm only familiar with the latter, and I would like
| to know if they are similar in concept or not.
| winwang wrote:
| That's a fantastic design idea, and it seems to require all
| the other lines to have the same indentation "prefix".
|
| Haven't used much C#, but I love Scala's `.stripPrefix` and
| `StringContext`.
| yegle wrote:
| > As Zig has only line-comments, this means that \n is always
| whitespace.
|
| Do I read this correctly that it replaces `\n` at the end of the
| line with a whitespace? CJK users probably won't be happy with
| the additional whitespaces.
| throwawaymaths wrote:
| that's not correct.
| ww520 wrote:
| Zig is great. I have fun writing in it. But there're a few things
| bug me.
|
| - Difficult to return a value from a block. Rust treats the value
| of the last expression of a block as the return value of the
| block. Have to jump through hoops in Zig to do it with label.
|
| - Unable to chain optional checks, e.g. a?.b?.c. Support for
| monadic types would be great so general chaining operations are
| supported.
|
| - Lack of lambda support. Function blocks are already supported
| in a number of places, i.e. the for-loop block and the catch
| block.
| n42 wrote:
| this is a really, really good article with a lot of nuance and a
| deep understanding of the tradeoffs in syntax design.
| unfortunately, it is evoking a lot of knee-jerk reactions from
| the title and emotional responses to surface level syntax
| aesthetics.
|
| the thing that stands out to me about Zig's syntax that makes it
| "lovely" (and I think matklad is getting at here), is there is
| both minimalism and consistency to the design, while ruthlessly
| prioritizing readability. and it's not the kind of surface level
| "aesthetically beautiful" readability that tickles the mind of an
| abstract thinker; it is brutalist in a way that leaves no room
| for surprise in an industrial application. it's really, really
| hard to balance syntax design like this, and Zig has done a
| lovely and respectable job at doing so.
| Twey wrote:
| > it's not the kind of surface level "aesthetically beautiful"
| readability that tickles the mind of an abstract thinker
|
| Rather, the sort of beauty it's going for here is _exactly_ the
| type of beauty that requires a bit of abstraction to
| appreciate: it 's not that the concrete syntax is visually
| beautiful per se so much as that it's elegantly exposing the
| abstract syntax, which is inherently more regular and
| unambiguous than the concrete syntax. It's the same reason
| S-exprs won over M-exprs: consistently good often wins over
| special-case great because the latter imposes the mental burden
| of trying to fit into the special case, while the former allows
| you to forget that the problem ever existed. To see a language
| do the opposite of this, look at C++: the syntax has been
| designed with many, many special cases that make specific
| constructs nicer to write, but the cost of that is that now you
| have to remember all of them (and account for all of them, if
| templating -- hence the 'new' uniform initialization
| syntax[1]).
|
| [1]: https://xkcd.com/927/
|
| This trade-off happens all the time in language design: you're
| looking for language that makes all the special cases nice _as
| a consequence of_ the general case, because _just_ being simple
| and consistent leads you to the Turing tarpit: you simplify the
| language by pushing all the complexity onto the programmer.
| n42 wrote:
| I considered making the case for the parallels to Lisp, but
| it's not an easy case to make. Zig is profoundly not a Lisp.
| However, in my opinion it embodies a lot of the spirit of it.
| A singular syntax for programming and metaprogramming, built
| around an internally consistent mental model.
|
| I don't really know how else to put it, but it's vaguely like
| a C derived spiritual cousin of Lisp with structs instead of
| lists.
| Twey wrote:
| I think because of the forces I talked about above we
| experience a repeating progression step in programming
| languages:
|
| - we have a language with a particular philosophy of
| development
|
| - we discover that some concept A is awkward to express in
| the language
|
| - we add a special case to the language to make it nicer
|
| - someone eventually invents a new base language that
| natively handles concept A nicely as part of its general
| model
|
| Lisp in some sense skipped a couple of those progressions:
| it had a very regular language that didn't necessarily have
| a story for things that people at the time cared about
| (like static memory management, in the guise of latency).
| But it's still a paragon of consistency in a usable high-
| level language.
|
| I agree that it's of course not correct to say that Zig is
| a descendent or modern equivalent of Lisp. It's more that
| the virtue that Lisp embodies over all else is a universal
| goal of language design, just one that has to be traded off
| against other things, and Zig has managed to do pretty well
| at it.
| scuff3d wrote:
| My only complaint about the article is that it doesn't mention
| error handling. Lol
|
| Zigs use of try/catch is incredible, and by far my favorite
| error handling of any language. I feel like it would have fit
| into this article.
| melodyogonna wrote:
| Very interesting, Zig seems really nice. There was a severe lack
| of resources when I tried to get into it few years ago, it is
| nice to see that situation improving in real time.
|
| The part about integer literal is similar to Mojo, with a comp-
| time type that has to be materialized to another type at runtime.
| In Mojo though this can be done implicitly so you don't need
| explicit casting.
| culebron21 wrote:
| I've been seeing articles on Zig for the last 2 years, and was
| interested, but it seems the language community is too far from
| my area of interest -- data and geospatial, and the tools in my
| sphere in Zig aren't mature enough. E.g. to parse a CSV, you have
| to use very simple packages or just use a tokenizer and parse the
| tokens yourself.
| MrBuddyCasino wrote:
| > I think Kotlin nails it: val, var, fun.
|
| Been saying this, and I find it strange how few agree.
| norir wrote:
| From my vantage point, Zig's syntax perfectly matches the
| language: it is ad-hoc, whimsical and serendipitous. It is
| lacking in grace, elegance and compassion.
| ruuda wrote:
| > as name of the type, I think I like void more than ()
|
| It's the wrong name though. In type theory, (), the type with one
| member, is traditionally called "Unit", while "Void" is the
| uninhabited type. Void is the return type of e.g. abort.
| ivanjermakov wrote:
| If I understood `abort` semantics correctly, it has a type of
| `never` or Rust's `!`. Which has a meaning "unobtainable value
| because control flow went somwhere else". `void` is closer to
| `unit` or `()` because it's the type with no allowed values.
|
| Cool trick: some languages (e.g. TypeScript) allow `void`
| generics making parameters of that type optional.
| bmacho wrote:
| I think you are correct, and it is indeed the name of the 1
| element type what should go there. However
| https://en.wikipedia.org/wiki/Unit_type says that Rust has it
| but not really, for me fn sayhi () -> () {
| println!("hi"); } fn main() -> () {
| sayhi( sayhi() ) }
|
| does not work.
| cornstalks wrote:
| It fulfills the same role as C and C++'s void type. I don't
| think most systems programmers care about type theorist
| bikeshedding about purity with formal theory.
|
| A ton of people coming to Zig are going to be coming from C and
| C++. void is fine.
| ivanjermakov wrote:
| > Almost always there is an up-front bound for the number of
| iterations until the break, and its worth asserting this bound,
| because debugging crashes is easier than debugging hangs.
|
| Perhaps in database systems domain yes, but in everything else
| unconditional loop is meant to loop indefinitely. Think event
| loops, web servers, dynamic length iterations. And in many cases
| `while` loop reads nicer when it has a break condition instead of
| a condition variable defined outside of the loop.
___________________________________________________________________
(page generated 2025-08-10 23:01 UTC)