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).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.
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.
https://ziglang.org/documentation/master/std/#std.sort.binar...
This is indeed a point which makes Zig inflexible.
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?
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'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...
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’ :)
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.
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;
fn ArrayListType(comptime T: type) type {
D: T ArrayListType(T)() {
struct ArrayListType(T) {
?
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?).
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.
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.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.
do_not_redeem•1h 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•1h ago
nateglims•39m ago