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.
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.
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.
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).https://github.com/ziglang/zig/issues/5038#issuecomment-2441...
A language hostile to LSP/intellisense.
Think I’d rather do the Point{} syntax.
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]The exception is languages with syntax that require a non-standard keyboard.
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.
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.
well, that's not really what it is doing - it is saying apply this function to these parameters.
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?
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).
Making all allocations explicit is one thing I do really like about Zig.
Heap allocating them is fairly rare, because most usages are in things like combinators, which have no reason to enlarge their scope like that.
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>.
https://ziglang.org/documentation/master/std/#std.sort.binar...
This is indeed a point which makes Zig inflexible.
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
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.
Go has a similar function declaration, and it supports anonymous functions/lambdas.
E.g. in go, an anonymous func like this could be defined as
foo := func(x int, _ int) int { … }
So I’d imagine in Zig it should be feasible to do something like
var foo = fn(x: i32, i32) i32 { … }
unless I’m missing something?
See
https://github.com/golang/go/issues/59122
https://github.com/golang/go/issues/21498
res := is.Map(func(i int)int{return i+1}).Filter(func(i int) bool { return i % 2 == 0 }).
Reduce(func(a, b int) int { return a + b })
vs res := is.Map((i) => i+1).Filter((i)=>i % 2 == 0).Reduce((a,b)=>a+b)
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.
Why don't they just add lambdas?
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/
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?
If you do want it, you have the option to, say, heap allocate.
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.
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?
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.
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?!
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.
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/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.
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.
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.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...
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!
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.
If the syntax were straightforward, plain old function types wouldn't be ‘fancy’ :)
If you're working with Roman numerals, division is a very fancy thing to do.
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.
(a -> b) -> c -> d
becomes C
D (*f(B (*)(A)))(C)
and it's no surprise that the former is considered much less fancy than the latter (even though the former is longer!).
Curly brace syntax would never have been invented in Europe (case in point: Python and Pascal).
Just like great musicians make the difference in the band, regardless of the instruments scattered around the studio.
You can also do something like:
```Point.zig x: i32, y: i32,
const Self = @This(); fn add(self: Self, other: Self) Self { // ... } ```
https://clang.llvm.org/docs/LanguageExtensions.html#vectors-...
https://clang.llvm.org/docs/LanguageExtensions.html#matrix-t...
But sure, if you only compare it with Rust, it is a big improvement.
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.
Too many symbols like ., :, @, ; etc just mess with my brain.
And no pointless semicolons.
const x: i32 = 92;
D has less syntax: const int x = 92;
Just for fun, read each declaration out loud. const std = @import("std");
D: import std;
import std=std;
fn ArrayListType(comptime T: type) type {
D: T ArrayListType(T)() {
struct ArrayListType(T) {
?
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.This reminds me of C in the 1970s where the compiler assumed every typo was a new variable of type int. Explicit is good.
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 intI 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.I like the syntaxes of each, although Ada is too verbose to me, and with Factor and Common Lisp I have a skill issue.
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.
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.
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.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?).
.. it is using \\
It’s some sort of mental glitch that a number of people fall into and I have absolutely no idea why.
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.
I would have probably gone with ` or something.
Choice of specific line-start marker aside, I think this is the best solution to the indented-string problem I've seen so far.
> 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.
`A
simple
formatted
string
`
?
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.
fn main() {
if something {
print(`A
simple
formatted
string`)
}
}
which looks ugly and confusing.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.
" one\n"
" two\n"
" three\n"
spoken as someone who found the syntax offensive when I first learned it.
People are having trouble distinguishing between '//' and '\\'.
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.
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.
const still_raw =
"const raw =
" "Roses are red
" " Violets are blue,
" "Sugar is sweet
" " And so are you.
" "
";
"
;
This cannot be confused with a string literal because a string literal cannot contain newline feeds. const raw =
"He said "Hello"
"to me
;
Wouldn't that be a mess to parse? How would you know that "He said " is not a string literal and that you have to continue parsing it as a multiline string? How would you distinguish an unclosed string literal from a multiline string?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.
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.
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.
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.
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)
Yes yes, I need a better PC. (rip)grep would still be faster, however, but I would use the IDE.
Such a good decision. There's no reason to use block comments in 2025.
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.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 :-)
Haven't used much C#, but I love Scala's `.stripPrefix` and `StringContext`.
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.
- 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.
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.
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]).
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.
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.
- 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.
Zig comptime operates a lot like very old school Lisp FEXPRS before the Lisp intelligentsia booted them out because FEXPRS were theoretically messy and hard to compile.
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.
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.
Been saying this, and I find it strange how few agree.
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.
Cool trick: some languages (e.g. TypeScript) allow `void` generics making parameters of that type optional.
This means that a function that returns an uninhabited type is statically known to not return -- since there's no way it could construct an uninhabited value for a return expression. It also means you can coerce a value of uninhabited type into any other type, because any code which recieves a value of an uninhabited type must be unreachable.
For instance, in Rust you can write the following:
let weekday_name: &str = match weekday_number {
1 => "Sunday",
2 => "Monday",
3 => "Tuesday",
4 => "Wednesday",
5 => "Thursday",
6 => "Friday",
7 => "Saturday",
i => panic!("invalid weekday number: {i});
};
Because panic! aborts the program and doesn't return, its return type is uninhabited. Thus, a panic! can be used in a context where a string is expected.A ton of people coming to Zig are going to be coming from C and C++. void is fine.
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.
do_not_redeem•9h ago
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•9h ago
nateglims•8h ago
hiccuphippo•6h ago