Yeah I found this article super weird. Explaining pointers & dereferencing is reasonable, but doing it in the context of Zig specifically, like Zig is the first language to feature dereferencing, is odd. Especially since pointers are such a fundamental part of low-level (really, any) programming. Also not sure why it's posted here?
Edit: Going through this author's website, it seems like a lot of their posts are about rediscovering low-level programming concepts through Zig. Like this article, where they discover you can't compare strings directly, and you have to use memcmp:
They claim that they blog because they "find that [they] retain things better when I write about them." No problem with that. Just a little odd to see on the hn front page, I suppose.
I think this is just "discovering". While you (and I) probably discovered these things with assembly or C languages, it's perfectly reasonable or even appropriate for newer generations to have these kinds of experiences with Zig or Rust.
Absolutely, like I said, there's no problem with what this person is doing or the way that they're exploring computers, I'm just confused as to why it's being posted here.
Maybe I'm wrong and this is new to a lot of people! I have a limited perspective based on my programming journey, which winds mostly through gamedev, graphics programming, and DSP, both (typically) low level domains. But I think if the title if the article were more accurate, (e.g. "What are pointers?"), my reaction would be more clear. I'm also kinda taken aback by the "old grey beard" comment. Look at all the kids using Rust, Zig, even C and tell me that this is obscure knowledge.
When I took my first programming class at RIT, visualizing the stack, variables, and pointers was one of the first classes we had. It's one of those beginner diagrams that I feel like everyone is familiar with. But I can understand that there are programming domains with equivalent complexity which don't require that base knowledge. I apologize if I came off as elitist.
I don't think people understand how abstracted most modern developers in today's world are.
If learning Piano was an example then everyone would have like you said learn the basic of everything and then build up on it. Modern day people learn multiple different chords and somehow string them together. If you do EE you have have learned how the piano works before you start playing the piano.
Worth remembering most people in programming today start with Javascript / Python or Ruby.
At least Python can be as complex as C++, it is a matter of actually bothering to go through all major manuals in https://docs.python.org/3.
Those languages are hardly more abstract than learning Lisp or Prolog back in the day, other than (with exception of JavaScript) finally embracing dynamic compilers.
It boils down to how much one actually cares to learn and is curious to improve themselves.
Author here. I agree this is a less-than captivating piece. I write a lot about Zig and wanted something I could reference from other pieces.
But, to answer your question directly: absolutely. In addition to writing a lot about it, I maintain some popular libraries and lurk in various communities. Let me assure you, beginner memory-related questions come up _all the time_. I'd break them down into three groups:
1 - Young developers who might have a bit of experience in JavaScript or python. Not sure how they're finding their way to Zig. Maybe from HN, maybe to do game development. I think some come to Zig specifically to learn this kind of stuff (I've always believed most programmers should know C. Learning Zig gets you the same fundamentals, and has a lot of QoL stuff).
2 - Hobbyist. Often python developers, often doing embedded stuff. Might be looking to write extensions in Zig (versus having to do it in C).
3 - Old programmers who have been using higher level languages for _decades_ and need a refresher. Hey, that's me!
Also, from learning human languages, it's a well-known lesson that phrasebook-type "this means this" translations (like some here are asking, from Zig to C/Rust) are useful for quick and dirty learning good enough for one trip, but long term learning needs this kind of a direct explanation.
1. It avoids the word (or syntax in this case) getting stuck in a double-indirection state, needing you to mentally translate it from Zig to C to what it actually means every time.
2. It avoids the learner attaching the wrong nuances to the word or syntax feature, based on the translation they're given, when the language they're learning has different nuances. In other words, it helps the learner see it as its own thing, and not be unduly colored by what they already know and find easy to grasp on to (even when it's subtly wrong).
> Also, from learning human languages, it's a well-known lesson that phrasebook-type "this means this" translations (like some here are asking, from Zig to C/Rust) are useful for quick and dirty learning good enough for one trip, but long term learning needs this kind of a direct explanation.
This is also good when the user already knows the concept. Like, I'm a reasonably competent Rust user, and when I started playing around with Zig I already understood the majority of concepts at play and just needed to know how Zig spelled them. But I still needed that more in-depth explanation for concepts I was less familiar with (comptime being the main one).
The author may not know C. As OP said, it's definitely the case that people interested in Zig may not have any interest in going through C first. They may eventually have to, as Zig interfaces heavily with C code and libs, but there's nothing wrong with going Zig first.
It feels a bit performative to me: the article goes out of way not to explain it by giving the C equivalent.
Perhaps some day Zig will have replaced C and beginners will come to Zig having never touched C, and in that context this approach makes sense - after all, you wouldn't litter an introductory article on C with comparisons to Algol. But today, surely the modal Zig beginner already knows enough C that the syntax would better be explained by reference to C.
And why change something like that? The world would be a better place, IMHO, if there were less different ways to write something from language to language.
Sometimes it seems like the change is just to make it different, not better.
I think Rust could use some syntax improvements for pointer manipulation. It has suffix .await, so maybe the community is ready for suffix .* for dereference now too. Visually scanning left and right, along with the extra parentheses, makes pointer-based code in Rust worse than C++ almost. A fully left-to-right syntax would be amazing. EDIT: found https://github.com/rust-lang/rust/issues/10011 and https://github.com/rust-lang/rfcs/pull/3577
I’ve gotta say, when I first learned Rust had gone its own way on await, I was heavily sceptical. But seeing the actual examples was pretty compelling.
Yeah I was skeptical too but it makes so much more sense. Now I'm constantly thinking "this is dumb" when using other languages with the "normal" syntax.
I wish they'd got it right for (de)referencing too though.
The fact that the author of Zig who's extremely well versed in C and C++ went this way should make you at least try to think why he went that way before dismissing it.
It's remarkably intuitive and sensible if you remember that . in Zig auto-dereferences for field access and then treat `*` in this construct as field name denoting the entire object.
Ada does the same exact thing, except there you write `p.all` instead.
In any case, while the exact syntax may not be ideal, using a postfix operator for dereferencing is vastly better than prefix in practice due to typical patterns of use. There's a reason why you end up writing () a lot in C code with heavy pointer operations - the things they end up mixed with most often turn out to have the wrong kind of priority and/or precedence more often than not. Things are much simpler when everything is postfix and code reads naturally left-to-right.
(I'd suggest the latter is now the more readable of the two).
Where there is a VFT in each of the xxx_out structures, and the calls simply returns the VFT, while the whole abstraction is stored/returned via the out arguments.
In general, the rule of thumb is that prefix and postfix operators don't mix well in a single expression - order of operations is confusing, and reading the code requires going back and forth to follow it.
In the ideal world, we wouldn't have unary prefix operators at all, but unfortunately unary +, -, and NOT are prefix mostly because they were that in math notation and got grandfathered in (bonus points to Smalltalk here for going with consistency here - "not" and "negated" are regular nullary methods there so it's postfix throughout!). However, these are rarely themselves an operand of another unary operator, so you can mostly deal with this by giving postfix higher precedence than prefix in all cases, so at least it's a simple rule. But then for pointer dereference, it is in fact common to have the result of a dereference itself be an operand in the middle of another expression.
So now you have some choices to make. If you make the pointer dereference prefix, then you don't need extra parentheses when applying other prefix operators to it, e.g. -*p or !*p. If you make it postfix, then you don't need extra parentheses when applying other postfix operators to it, e.g. (using Pascal-style ^ for dereferencing) p^[0] or p^.x. Alternatively, you could add special postfix operators that desugar into the combination but avoid those extra parens, which is what C did with -> for field access.
(Technically, you could also make everything prefix instead, e.g. field access ALGOL 68 style: `month OF birthDate OF person`. But this is very counter-intuitive with indexing, and also makes code completion unusable, so it's not a serious option.)
In Python, there is no .await. But I can't remember seeing (await (await for).bar) ever--meaning it is very unlikely. It is usually written as:
bar = await foo()
await bar.coro(*args)
Some people hate that calling functions of different colors is syntactically distinct. On the contrary, I find it beneficial that suspension points stand out. Unlike code with preemptive threads that is much harder to reason about.
You will know because any text editor or IDE worth the name will light up that ".await" in such a way you will immediately know it's not a method call or a struct field. The entire construct, including the dot, is a postfix keyword.
this is not just a preference but a practical matter, esp. for reading nd checking other People's code. did you solve the puzzle at the end with high confidence?
You mean `foo = *bar[10]`? That’s equivalent to `bar[10][0][0]`, i.e. the array element access is done first, then the retrieved element is dereferenced twice. I’m quite confident in this, but I’m a systems programmer working in C++, so that’s my bread and butter.
This syntax is less arbitrary than C's. It draws a syntactic parallel between accessing a single member and accessing "all members". (by using pattern-matching-like syntax)
It makes the language more consistent and one's mental model of it smaller. (Even though I doubt that patterns other than the Kleene star would work)
I don't actually agree that it makes the mental model smaller. I see something that is just different from what I expect, is cognitively jarring, interrupts the flow of what I'm doing, and forces me to focus on something I shouldn't need to.
But it seems like I'm in the minority, so maybe it's just "old man shouts at clouds", I'll just ignore the language and move on :)
Yes, but unlike C's terribly confusing "declaration follows usage" style, which no tells you about btw, zig's pointer syntax doesn’t turn into a nightmarish puzzle.
In Zig, you can pretty much read the types aloud and it makes, your brain does not need to peek for parsing.
For me it's too late, I got used to the C way but I still want the better thing to get adopted.
No more int ((foo)(int))[5]; nonsense—just T, [N]T, or *T, making intent crystal clear.
Yeah, not sure where that came from. The ANSI C standard was extensively documented in books and articles and specs at all levels of rigor starting from the mid-80's. No one has ever lacked for a reference for how function pointer declaration looks.
That said, C's function pointer declaration syntax is indeed awful. But really that's because ANSI took a very hardline "no incompatible changes" tact when adding prototypes to the language, which limited the ways they could express them. That decision is one of the reasons we're still writing C today. Any yahoo can come up with a new language, kids do it all the time. ANSI's job was to add features to the language in which Unix was already written.
I.e. C's
is in Zig.