Zig is a very interesting language. You are able to do thing that would require wizard level skills in C with simple plain language construct. Like serializing/deserializing an enum variants over the wire for example in the most efficient way (look at std.json.{parser, stringify} [0]).
> soon we’ll also have a package manager
This. If they succeed to do this (and I have my doubts) it will be a paradigm shift for low-level "system" programming.
Let's hope this is not vaporware.
Finally, it's relatively easy to contribute to Zig. Andrew Kelley is very opinionated on where the language should go with an emphasis on simplicity which sometimes can make things awkward but he is also persuasive and welcoming. I have been using Zig for a month and I am still positively surprised so he must be onto something.
I sincerely approve of the skepticism. I can assure you there is already a very large fire under my ass to get this shipped.
To provide some more context, here is a snippet from the latest release notes[0]:
> Having a package manager built into the Zig compiler is a long-anticipated feature. Zig 0.8.0 does not have this feature.
> If the package manager works well, people will use it, which means building Zig projects will involve compiling more lines of Zig code, which means the Zig compiler must get faster, better at incremental compilation, and better at resource management.
> Therefore, the package manager depends on finishing the Self-Hosted Compiler, since it is planned to have these improved performance characteristics, while the Bootstrap Compiler is not planned to have them.
I can't help but mention, I am incredibly excited about how well the self-hosted compiler is coming along. Progress is swift, and all the ambitious design decisions (fully incremental compilation[1], extremely fast debug builds[2], low memory usage, multi-threaded) are intact. I anticipate it to make quite a splash once it becomes generally usable.
I don't expect the package manager to take nearly as long to develop.
> ...I can assure you there is already a very large fire under my ass to get this shipped ... [followed by a short, easy-to-follow explanation as to why it's taking so long]
I'm going to check out Zig because of your response.
I love coming across comments like this one on HN.
Yeah I watched a couple of his talks, enjoyed every word. There is another interesting language (won't mention the name) but the creator won't let the public in and has a superiority complex, turned me off instantly. I believe Zig will blow up just because of the strong vision but without being a dick about it. It's next on my list for sure.
I feel Elm's growth has been stunted because the creator is actively hostile with whoever disagrees with him. I remember the infamous "leaving elm" post which was handled so poorly by Elm's author.
If they had handled it well, elm would have definitely seen a large uptick in adoption
Parent poster 99% is referring to Jai. I'm of the same general opinion: interesting language, I've spent a lot of time watching jblow talk about it on Twitch, and it would be nice if it succeeded, unfortunately I have my doubts given jblow's attitude both in terms of external communication and within the development team [0].
There really isn't any call to trash Elm and its creator like this.
There's very little evidence of ongoing hostility from Evan - I pay attention to his work and writings and find he's rarely if ever hostile. In fact he's very mild-mannered, and is painstakingly detailed about explaining the reasons for why he does things the way he does, over and over again.
Perhaps that wore thin once or twice in the face of repeated criticism and demands. We've all seen way worse.
Yes, sticking to his vision has excluded ideas and contributions, and made some people very angry. There have been several high profile blogposts and twiits painting him as some kind of despot. And when other overzealous developers swooped in to defend his work, it just fed the meme, one which people on HN like to repeat every chance they get.
Well, I for one am glad he's a 'code despot' - considering how many people keep demanding that they dilute Elm's guarantees for the sake of JS library interop or marginally less boilerplate. Any bending to these demands and Elm today would be just another framework with a slightly different syntax, with none of guarantees that allow for the innovative tooling and libraries [1] we see coming out of the community.
Whether you agree with Evan's vision or not, Elm is doing just fine meeting its stated goals and is a fantastic and delightful technology and toolchain.
So please give over with trashing other people's hard work. If you don't like it, just don't use Elm.
[1] for example, the compiler's dead code elimination capabilities, elm-review's tco detection and other amazing auto-fixes, lamdera, etc.
Elm has done some great work, even the ones, who left elm for other tech, acknowledge elm's elegant design.
However, elm is not pragmatic. the issues mentioned in Luke plant's "leaving elm" [0] are valid. and Evan's response to that was far from satisfactory[1] and this has affected Elm's popularity.
The disagreement between BDFL and community has happened in many other cases as well. and there is a way to solve them amicably. Most recently, Vue faced it when community caused uproar over composition API RFC. and Vue solved it nicely and amicably[2]. If Elm has followed similar approach, Elm too would be praised for it, and gained even more popularity.
I think you're saying Evan is the BDFL (Benevolent Dictator for Life) for Elm, except it's interesting you chose the word 'despot' because many of the decisions made with Elm are far from benevolent (like preventing certain library usage and interop if the code isn't hosted on github, within the elm org).
The criticism is valid. Haskell is painfully strict and, still, it offers various ways out of its guarantees. Rust is painfully strict and gives you `unsafe`. Neither of them break your build if you don't host your code in a specific github org.
I purposely avoided 'BDFL': too much precedent as to what that means!
Limiting certain kinds of js access to vetted libraries is benevolent, if that's part of the language's goals. Likewise the lack of type classes or mutability/unsafe escape hatches.
Elm's developers do not enforce constraints to be 'hostile' or 'far from benevolant'. It's a tradeoff - language power for certain guarantees, guarantees that not even Haskell can provide.
Unfortunately, this sometimes means trying to constrain access to the completely unconstrained js library ecosystem, so the solution, at least for now, is a bit of a blunt weapon.
Note that Elm doesn't 'break your build' if you don't use github - that's a constraint on linking to official libraries only. I don't use github and my code works just fine.
Thanks for all your work! I ported a bunch of basic data-structures from C++ to Zig very early on (right as y'all began exposing generics). Even though the code no longer compiles, still worth learning The Zig Way™.
Hi Andy, I'm really intrigued by Zig's features - you're definitely solving the right and hard problems for C developers like myself.
If I may, my only piece of feedback (as someone who looked at Zig with fresh eyes) is that the syntax has lots of special chars (@, !void, .{}, &, etc). Is there a rationale published for these and other design choices (even if it's "I like it that way"). I would have expected a language with ambitious goals like Zig to take a stab at coming up with a grammar that's even simpler and cleaner than C.
Regardless, getting a new programming language working, noticed and used is monumental effort, so I wish you best of luck with Zig!
One of the things after working with zig for a bit, is that the dangerous stuff is cumbersome to write (not hard to read, though), and has lots of fiddly bits like @ signs... So your eye is drawn to it as if to say "here are the parts of the code that need careful review".
Relative to c, though there are imo fewer special chars:. Zig adds ! and ?, Which are there for really good reason, but subtracts for example <> and #.
The hardest thing is the . syntax for anonymous structs and tuples, but to be fair it is very consistent, and so many times I'd think,"hey I wonder if..." And then try it with the syntax and of course it works.
>If I may, my only piece of feedback (as someone who looked at Zig with fresh eyes) is that the syntax has lots of special chars (@, !void, .{}, &, etc). Is there a rationale published for these and other design choices (even if it's "I like it that way"). I would have expected a language with ambitious goals like Zig to take a stab at coming up with a grammar that's even simpler and cleaner than C.
That is sadly my gripe with a lot of the newer languages too. I have been looking for something to replace C for a long time, something that makes it easier to not shoot yourself in the foot. I just find most of them significantly less readable than C.
Rust seems to be going down the route of C++, hence I do not deem it a safe language at all, as I have an exceedingly hard time parsing the source and figuring out what is going on.
Zig is WAY better in that regard, but as OP mentions: all the special characters make things more difficult to parse. Calls like these (taken from the introduction page) contain so much visual clutter:
try expect(@call(.{}, add, .{3, 9}) == 12);
I sadly doubt any of this is possible to change at this stage anymore. Zig definitely seems the most promising C replacement with regards to tooling, especially cross compiling and C interop are a breeze. Truly inspiring work!
The only new C-like language that really nailed it for me syntax wise would be Nim. Very clean and easy to reason about, which - from a programmer perspective - makes it exceedingly safe to program in. It just seemingly lacks most of the safety guarantees that the compilers of other, newer languages provide.
Isn't the example difficult to parse only because it's unfamiliar to you? As someone who has never used zig, I feel like 5 minutes of study would make this line easy to read.
You should take a closer look at Nim. I suspect it probably has all that you want, but said features may not be "by default/well advertised" (yet). Alternatively, you should be more specific about what you think it lacks.
Will the compiler have something like go's ability to vendor code locally? I've worked on some science projects and this has been a hard requirement on some systems for tape archive storage.
Zig's build system has a first-class notion of packages that satisfies exactly this requirement. The Ziglearn documentation[0] has more details.
To make things a little more interesting though, Zig's shape in this regard should be roughly congruent to C's, so you should be able to do anything with Zig that you can with C. This includes linking against shared objects for C libraries like, e.g., SDL2[1] or building mixed-language Zig+C projects[2].
I've been toying with Zig over the summer, and I'm really impressed by the build system, general ergonomics and the explicitness of the language.
The only thing I've found being a pain point is C's biggest pain point: the lack of a proper string type. Even though I've worked for years in the past with C and C++, I still get tripped up, and in Zig I keep on being confused whether I should use raw byte arrays, or sentinel-terminated arrays for certain string manipulations. Maybe I'm not familiar with common idioms in Zig that might help avoid this C-style tracking of string/buffer lengths malarkey that I tend to end up with where a slice is not appropriate.
Maybe a dynamic-length string library is the way forward.
I've always tried as much as possible to treat strings as just opaque data and never look into them, which tends to work well, but in some domains you really need to look at and massage the characters/codepoints/grapheme clusters, and the lack of a first-citizen UTF-8-aware string type is, I think, a bit unfortunate in this day and age. I understand having one of those could make C interop a bit gnarlier (I think Odin's approach of having two separate string types -- one of which is just meant to be used for interop -- should be workable).
Oh, and the ecosystem of libraries is still young. I needed to format timestamps as human readable date/time to a file, and had to step down to C to do something that basic[1], but it'll get there in time, I'm sure.
[1]: "basic" from the perspective of the end-user of the language. Having written a (not even fully featured) date/time library in C++, I consider date and time to be one of the hardest domains I've ever had to work in.
Note that while strings are generally opaque bytes in Zig, there are standard library functions[0] to work with them as Unicode codepoints and such. But Unicode is quite large and complicated, and things like grapheme clusters aren't in the standard library (yet?). I also believe that that module is planned to be rewritten. So it's more the case that Zig plans to someday support Unicode at the standard library level, but not as a specific type.
Then again, I'm not sure exactly what a type would be for Unicode. Basically just a flag that the array of bytes has been checked and is valid? It's nice to let the type system help you enforce your application boundaries, but I think you could wrap it pretty easily in a struct of your own if you needed that.
> Basically just a flag that the array of bytes has been checked and is valid?
In something like Zig I think it's OK that it's merely a stated assumption that these bytes are UTF-8, not actually checked - so long as people take that seriously.
It's nice in code that maybe isn't very concerned with such things to be able to know that any "string" is actually text we can display, output to a console, something like that. Not for example a TCP/IP packet we haven't decoded yet, or the first 32 bytes of a JPEG image.
Programmers who aren't writing firmware for a vacuum cleaner or washing machine, probably want string literals like "this" in their code, and you'd naturally want those to have a type, for which a string type is the obvious fit. I believe in 2021 this type should obviously be UTF-8. "It's just some bytes" is far from useless, but it isn't much of a string type.
I was sceptical at first about Rust's choice to build in str (immutable UTF-8 string slices), because that seems like a relatively high level concept. But unlike std::string::String, str isn't that tricky after all. See, the only place such things would come from (not having std::string::String to make new ones) is the program source, and our compiler is necessarily already reading the program source, so it follows that the compiler must know how the source is encoded etc and thus it does actually know exactly what those literal strings are. If your literal wasn't Unicode, that wasn't a Rust program and it won't compile.
>In something like Zig I think it's OK that it's merely a stated assumption that these bytes are UTF-8, not actually checked - so long as people take that seriously.
And also as long as all supported operations on those bytes still result in utf-8 strings (there could be unsafe operations too, like splitting at a random byte offset, but there should be a clearly marked safe set that wont corrupt a valid utf-8 byte sequence).
>*It's nice to let the type system help you enforce your application boundaries, but I think you could wrap it pretty easily in a struct of your own if you needed that.
Well, as you said "Unicode is quite large and complicated".
And in 2021 it's not just anglosaxon software users anymore (if they ever were), good unicode lib if not type is a must.
> I've always tried as much as possible to treat strings as just opaque data and never look into them, which tends to work well, but in some domains you really need to look at and massage the characters/codepoints/grapheme clusters, and the lack of a first-citizen UTF-8-aware string type is, I think, a bit unfortunate in this day and age.
You don't need a string type for that, you just need routines that handle UTF-8 strings, like utfcpp (https://github.com/nemtrif/utfcpp).
Sure, you're not wrong. For my purposes that'd work, but I'm of the opinion that delegating that to an optional library will make a lot of developers lazy and not even bother thinking about Unicode/i18n/l10n issues at all. I've seen so much code, in a multitude of languages that is oblivious to it, but even if it's reified in a type or even having some form of language support, I guess you can argue that people can still ignore it.
In addition, I should add there are synergy effects in having the community (and the major libraries) settling on a common way, and you'll only really get that if there is a built-in.
However, if the philosophy here is to be unopinionated and optimize for flexibility over everything else, I feel I can't really fault them for making that choice.
That was basically the state of affairs with strings in Python 2, and although the transition was awful, I'm really, really appreciative of Python 3 now having clearly-separated str and bytes types.
If it's not a single official library, then this will just end up in a future mess...
Like in any language with any lib that is not self-contained (cuts across all aspects of a program and can be used anywhere) and has many different implementations and no sanctioned one.
I wouldn't use the word "require", and perhaps you didn't mean it that strongly. Don't get me wrong: I could get by without and achieve what I needed to, but to be honest it didn't feel great.
I was doing some pretty gnarly high-level filesystem work (transforming an unstructured tree of files/folders into a more structured tree following certain naming and categorization conventions -- a lot of parsing/building/concatenating paths). I have been working with higher-level languages for the past decade or so, but have had a wanderlust to get away from garbage collected langauges and go back to my roots a bit, so I've been comparing some of the newer options in the "improved C"/"C++ replacement" space.
It may very well be that I'm not the target demographic for Zig, and that's totally fair, but to me it felt pretty jarring to go back to working with C-style character (byte) arrays and all the (well-known) issues with which they are fraught. I didn't feel like Zig was an improvement over C in the string handling area (as opposed to all other areas), whereas languages like Go and Rust are.
Keep in mind that filesystem paths aren't strings. On Linux, they are raw bytes without any fixed encoding (but usually UTF-8 on UTF-8-based locales), and on Windows, they are sequences of 16-bit codepoints which are expected to be UTF-16 but not validated.
Rust's OsStr is my favorite approach so far. It stores Linux's raw bytes as-is, and stores Windows's possibly-valid UTF-16 as WTF-8. This makes path management "just work", with the ability to operate normally on invalid UTF-8 or UTF-16 paths, and zero-copy conversion from UTF-8/ASCII strings to OsStr (though converting OsStr into UTF-16 requires parsing). (Qt's QString-based file dialogs on Linux fail to convert invalid UTF-8 paths like those in https://github.com/petrosagg/wtfiles into QString, causing Qt-based apps to open/save the wrong paths.)
However there are difficulties in printing an OsStr. For example, a file dialog that shows filenames as raw bytes can't show non-Latin/Unicode characters in a human-readable form, and a file dialog that shows filenames as Unicode strings can't handle invalid Unicode filenames. GTK3 file dialogs show filenames as Unicode strings, and when encountering files with invalid Unicode names, instead displays "file�name.txt (invalid encoding)".
Worse yet, how should a file dialog allow users to rename files? If it's based around byte arrays, the user can't enter Unicode characters directly, and if it's based around Unicode (or a locale-specific text encoding), it can't display existing files with invalid Unicode/etc. in the name (probably not an issue if it allows the user to rename to a valid name), nor allow users to enter invalid Unicode (which is not an issue IMO).
The difficulty of printing OsStr is nothing to do with Rust, it's really just the difficulty of printing Linux file names in any context given you don't know what the non-UTF8 bytes mean.
Apple’s fs do guarantee the paths are correct, as in, valid whatever encoding this has nothing to do with glyphs.
APFS also does not perform any normalisation while HFS+ uses a custom variant if NFD. While HFS+’s normalisation has its issues and critics, APFS’s lack of normalisation is probably worse: https://eclecticlight.co/2017/04/06/apfs-is-currently-unusab...
For strings, I believe Rust's approach of a dynamic `String` and a view `str` is the right approach.
For a minimal std, maybe Zig can have a standard `str` type which is used for all functions that don't need to grow the string and the `String` type is provided by any 3rd party library which is expected to provide a conversion function from `String` to `str`.
These strings types could or could not have a requirement of being Utf-8. Maybe there is a builtin wrapper to convert them. So you can any dynamic byte array which can be converted to `str` (just a pointer and length, no validation) which can be later converted to `utf8` (str but validated). A `String` which guarantees Utf-8 could directly convert to `utf8` to save validation costs.
I've been paying attention to Zig posts on HN, mostly just because it seems well-liked and I think Andrew Kelley is interesting/smart. Would it be fair to say that Zig is to C, what Rust is to C++? Or are they both just kind of...low-ish-level systems languages solving similar problems, differently?
> Would it be fair to say that Zig is to C, what Rust is to C++?
I don't think that's the parallel I would draw.
Rather, I think both Zig and Rust aim to serve the use cases C and C++ do, but Zig and Rust pick different points on the tradeoffs involving safety. Zig feels like a "better C", in the sense of bringing modern language features to C, but it chooses safer rather than safe. Rust supplies modern language features as well, and chooses to prioritize safe; sometimes that comes at the expense of other factors, such as productivity or compile time. I personally prefer the point on the spectrum that Rust chose, but I think Zig still offers improvements over C.
disclaimer: I haven't actually used either, just done a lot of interested reading and a little toying around.
One thing that impressed me about Zig is how simple the language is. I get the impression that it would take very little time to properly learn the language and become effective at it.
Rust, on the other hand, has a whole lot of complexity even without the ownership semantics. So much to learn and understand. And, in some cases, work around. It seems, from looking at others' code, that there is a very strong temptation to lean on `unsafe` instead of figuring out how to solve a problem within the constraints it normally applies.
That has me wondering if there's really all that much safety difference in practice? Simpler code is generally easier to understand, and it's generally easier to find and fix bugs in code that's easier to understand. I can imagine a world where it's a tradeoff between, "You definitely won't have any of this relatively narrow category of bug, unless of course you opt out of the static checks, which you will probably do sometimes, even though we tell you you shouldn't," and, "There are no particular guarantees, but you're generally less likely to have any of this broader category of bug."
"simple" is, unfortunately, not a simple concept, nor does everyone agree on its effects on programs written in a language that is or is not simple.
Some people do believe that, let's say "conceptually parsimonious" languages (using complicated words to describe simplicity is amusing to me, sorry) do exactly what you say. Others believe that complexity inherently exists, and you can put it in the language, where a compiler can tirelessly check certain properties for you or a runtime can do magical complicated work on your behalf, or you can put it in the programs, where users have to check such things themselves.
Another way I heard this expressed one time is Ruby vs Python. I once heard someone talk about how they preferred Python as a language more, because it was simpler, but enjoyed actually programming in Ruby more, even thought it was more complex, and they value simplicity. The reason is that all of that nasty ugly stuff Ruby lets you do makes you be able to make extremely nice APIs. Things you couldn't do, or just folks don't do, in Python. And therefore they ended up liking using Ruby more in practice.
I suspect that this is a topic that we, as a profession, will debate about endlessly.
> Others believe that complexity inherently exists, and you can put it in the language, where a compiler can tirelessly check certain properties for you or a runtime can do magical complicated work on your behalf, or you can put it in the programs, where users have to check such things themselves.
I think we'd all agree there's a spectrum, with, say, Assembly on one end and Idris on the other, where neither Zig nor Rust are near either extreme, and are actually rather close to each other. It's not a matter of accepting or rejecting a principle, but on picking slightly different sweet spots (hopefully) on a tradeoff spectrum.
Rust doesn't check at compile-time most things that could be checked at compile-time. In fact both language check almost the same things at compile-time, and almost the same things at runtime. The design differences come down to one or two things that are clearly tricky and that neither language "does for you" -- i.e. you still need to think about them -- but are, indeed, checked in different ways.
There is also the notion of languages you love writing code in vs languages you like reading code in. Languages with more powerful abstraction abilities bring joy to the writer but make things tedious for the reader.
I don't agree with that. For example, I think it brings me joy as a reader that I can tell at a glance from a function signature which arguments are modified and which ones are just read. Without the abstraction of mutable and immutable references I would have to read the entire source code of the function and every function it calls to know for sure what it does with the pointers. It also brings me joy as a reader that common functionality like equality comparisons or string formatting can be derived by the compiler, instead of having separate hand-made implementations for each data structure, which might have subtle differences.
> Languages with more powerful abstraction abilities bring joy to the writer but make things tedious for the reader.
I find map/filter to be easier to read than a C-style for loop. Things that are conceptually denser make discussion between experts easier, even if it makes things harder for non-experts. This is why jargon exists, for example.
Ah yes - that makes sense. I should have been more precise - I meant abstractions that allow you to add opaqueness (like macros and operator overloading). Again culture plays a role here too - some communities normalize such abstractions more than others.
In short I believe languages that one can pick up in a couple of days and then read code where reasoning of the code is highly local would stand a higher chance of being perceived as simple.
As always it depends. If you're thinking about preprocessor macros and operator overloading from C++, sure, those can be annoying, it's more to do with C++'s implementation and usage of them than the features themselves. You might want to try Common Lisp sometime; so much of the base language is made up of macros without which programs would be neither pleasant to read or write, and the language itself provides facilities to ask "ok but what function(s) are actually going to get called with this data" so that even not-so-local things like e.g. transparent logging of a call's input/output become visible if you need to know. But CL is not a language one can pick up in a couple of days -- albeit CL shops report success in getting new hires to be productive after a week or two of reading a book and the company code, which is a common onboarding time at many companies with any language.
Programmers notoriously conflate "simple" and "easy" (classic talk: https://www.infoq.com/presentations/Simple-Made-Easy/) and so I believe languages that are easy for a lot of programmers will also be perceived as simple, whether or not that's accurate.
Without powerful abstraction (of course used sparingly) you will just copy-paste the same logic in multiple places. Which is error prone and unmaintainable. And I would argue that even though a small part of it may be easier to read, you will have a harder time getting the big picture with low abstraction.
On the one hand, c_foo is shorter and doesn't use strange symbols. On the other hand, rust_foo doesn't leave you guessing on whether the returned value needs to be free()d or not, and whether the heap allocation backing `bar` can be free()d after the call or not.
The simplicity of the language is directly correlated to the simplicity of that line. The fact that the language makes relationships between inputs and outputs explicit is a feature of the language, because of its borrow checker, at the expense of being syntactically verbose compared to C.
Not at all. A single line will use a tiny subset of language features. It will show you nothing about the overall complexity of the language, just about those things it happens to use.
In my experience unsafe tends to be used really sparingly by most users, if at all. And besides, unsafe doesn't really turn off static checks - it allows you to do a few extra things that can't be proven to be safe statically, in tightly scoped specially annotated regions. It's mostly about bending the rules temporarily, for 1-5 lines of code at a time.
Side note: Nothing is provable in rust because it does not have a formal, verifiable specification. We have a reference implementation that we believe does the things it says it does on one particular architecture. But trust, not proofs, is indeed required. >:) (don't downvote murder me, I love rust)
> It seems, from looking at others' code, that there is a very strong temptation to lean on `unsafe` instead of figuring out how to solve a problem within the constraints it normally applies.
Are you sure those examples of unsafe Rust code were written using unsafe in order to work around safety constraints due to difficulty?
I ask because there are some very real and valid use cases for unsafe code in Rust that have nothing to do with the fact that writing the code safely might be more difficult.
Not sure at all - I have effectively zero working knowledge of Rust - and certainly not looking to second-guess people's motivations for using the feature.
The big bias I'm working from here is that, as an outsider, I have a hard time reconciling all the big talk about how Rust makes it impossible to experience certain classes of bugs, with the existence of a known set of things that its type checker can't check, and which includes a lot of things that are difficult to avoid. One could be forgiven for thinking that the Rust community has a habit of keeping its fingers crossed behind its back when talking.
I don't want to make the perfect the enemy of the good, of course. I realize the feature is there for an important reason. But it sure looks to me like Zig is also good despite being imperfect. Perhaps they're both similarly good languages.
Proper use of 'unsafe' in Rust is to write safe code that the compiler cannot prove is safe. In fact, this is done to implement many parts of Rust's standard library. See the split_at_mut example in
The spec here is to turn one mutable slice into two non-overlapping mutable slices. The operation does not violate memory safety; we wind up with only one mutable reference to any part of the object. But the compiler does not know how to verify that the programmer has done this correctly. Here, 'unsafe' means 'I certify that this is safe, use that as a lemma to do further checking'.
All systems programming languages need the ability to do unsafe stuff, the big difference between C , C++, Objective-C and all the remaining system programming languages since JOVIAL and NEWP, is the explicit unsafe code blocks that make that visible.
It is a big difference to have it on your face that something might need to be properly double checked and having each line of code as possible security exploit.
> the big difference between C , C++, Objective-C and all the remaining system programming languages since JOVIAL and NEWP, is the explicit unsafe code blocks that make that visible.
Would you consider Oberon to belong in the former or latter category? On one hand, there's no explicit code blocks as such (syntactically). On the other hand, unsafe primitive operations are limited to the SYSTEM module, so functions calling operations in the SYSTEM module can be considered doing unsafe things.
Just like the languages that have unsafe code blocks it is relatively easy to track down unsafe modules, same thing.
And just like languages with unsafe code blocks, there are compilers that have switches to forbid the use of tainted modules unless explicitly allowed.
In any case, way better than what C, C++ and Objective-C offer, regarding security per line of code.
No type checker can catch all buggy programs (that involve solving the halting problem). In practice, Rust strikes a good balance: in over 120,000 LoC of Rust code that I have helped write and maintain, we have maybe 10 lines of unsafe, with performance matching or exceeding that of the C/C++ counterparts.
> No type checker can catch all buggy programs (that involve solving the halting problem).
It's far worse than that, the problem is that the machine can't know what was intended. The result 5 from calling sum([1,2,1,1]) makes sense, but alas this function was intended to produce the product not the sum, the inputs were supposed to be [1,2,3,4] and the desired result was 24... the machine can't hope to guess that.
A program may be buggy even if it its meaning is transparent and exactly reflects its effect, simply because it doesn't reflect the intention of the person who wrote the wrong program.
This quote is attributed to Babbage:
'On two occasions I have been asked, – "Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out?" ... I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question'
But that is what we wanted, even if we obviously can't have it.
I think they solve some similar and some different problems, with some similarities and differences!
The biggest difference in the problems they solve, in my view, is that Zig does not aim for safety. It'll be easier to write safe programs yourself with Zig than it might be with C, but it doesn't give you any guarantees.
Zig also seems like it may be a better fit for embedded programming; there's a big community around embedded Rust, and it sounds like there's a lot of progress, but still an uphill battle.
But yes, Zig:Rust :: C:C++ has merit in that Zig is a much smaller, less feature-rich language.
I write/maintain multiple code bases for XMega, SAMD, and STM chips. The kind where 32K ram is cool and 256K flash is lots. I'm curious if either of these is ready for normal humans to cross compile for these kinds of targets (M0 and the ilk). Zig looks very interesting to me.
Many people say this, and there's some truth to it, but like all analogies, it can be useful in some contexts but not others.
The issue with this analogy is that it assumes that C, one of the languages used in the most diverse set of circumstances ever, means the same thing to everyone. Same with C++, frankly.
> Would it be fair to say that Zig is to C, what Rust is to C++?
There's definitely something to that analogy, but I think it also misses a lot of really important details. For example, Zig has generics, which right off the bat makes it hard to say that it's "like C". Also Rust enforces memory safety, which isn't like C or C++.
i love that zig doesn't have a special syntax for generics, it just allows anything to be resolved at compile-time -- including types. which gives you generics 'for free'.
I've only cursory of Zig, so bear with me. Does this mean that Zig templates are like C++, that is, duck typing? Or is there a notion of constraining the type arguments?
It's duck typed but it's not based on a funky and limited syntax, but rather types at comptime become normal arguments to functions that you can then inspect using normal Zig code.
It's duck typed but let's say your "template" takes an integer and divides by two depending on some boolean (perhaps you need to return a lookup table of sometimes half the bitwidth of an integer type). That function called by your template at compile time to maybe-divide-by-two is in the same language as runtime zig, and you could even conceivably call that very same function as a compiled entity in the runtime context.
I think that's correct with some overlap, yes. Zig's explicit goal - as stated by the creator - is to replace C. Nothing more, nothing less.
I feel like Rust wants to replace C but it also wants to replace C++. And given the complexity difference between the two languages, that means that Rust will end up closer to C++ than to C.
So there's some overlap based on how Rust positions itself, but not based on how Zig positions itself.
> I feel like Rust wants to replace C but it also wants to replace C++.
Rust wants to make systems programming memory safe (and type safe while at it). It's not really about any specific language, it's about dragging the field forwards on the safety front.
Well it's not really Rust, it's the Rust community, which kinda wrestled it away from Graydon Hoare: the original inception of Rust was more of an applications language (what with the evented model and the split stack and the planned-though-never-really-implemented GC'd pointers) — which explains part of the historical confusion with / comparison to Go.
But then a critical mass of people took a look at this early rust and figured "we've got plenty of memory-safe and type-safe (ish) languages for applications, but this thing has precise control over memory and lifetimes and shit, we could actually build reliable infrastructure with that", and it coincided with a lot of memory safety issues news (which has been continuing ever since), and the rest is history, pretty much.
We already have c/c++ as low level bases, now zig/rust are coming for the throne, I really hope contenders dont keep spawning like rabbits. If everyone got behind a smaller # of initiatives the worlds codebase would be simpler going forward?
I'm imaging some poor software engineer in 2065, having to maintain an enterprise legacy stack... levels of FORTRAN/COBOL->C->C++->ZIG->RUST->PERL->PYTHON.... all the way up....JS..etc..
I don't want contenders to spawn like rabbits. I want the industry to coalesce around just a few contenders to maximise network effects so as to kill off C and C++ more effectively.
While I would absolutely love to have that job, I’m afraid I’ll never get to do it. Language cross-compilers seem to keep getting better. It may be that case that one day, you will run a legacy program through a babelfish and get something understandable to you back out.
Of course, the complexity might explode from there…or you might just not really notice it all that often. Anybody familiar with LLVM or WASM or heck even a language that can transpile to JavaScript can probably relate to this possibility.
The thing that’s harder to fathom is going from low-level to high-level. High-level -> intermediary low-level -> high-level is fathomable though.
> I really hope contenders dont keep spawning like rabbits
I do, because that would mean that more attention must be paid to make languages work together, instead of each language ecosystem becoming its own little island. Maintaining a project built from a dozen languages (where each language does one thing well) should be just as simple as maintaining a mono-language project.
> I'm imaging some poor software engineer in 2065, having to maintain an enterprise legacy stack... levels of FORTRAN/COBOL->C->C++->ZIG->RUST->PERL->PYTHON.... all the way up....JS..etc..
I have good news and bad news: That programmer will not know any of those languages, as they will just program in English and GPT-3000 will convert it into code.
The reason that we have programming languages at all is that English and all other natural languages are too ambiguous to be used for this purpose.
GPT-3000 will have no problem understanding some interpretation of your English (or Spanish or Mandarin) "program", but will it be the right one? How long will it take to get GPT-3000 to understand what you mean when you can only talk to it in English (or Spanish or Mandarin) ?
So like months and years? On every project I’ve worked on, the programmers conception of the requirements evolves until the minute they stop working on it. And they only understand the part they worked on, not the whole thing
The discussion is not about understanding "requirements".
A piece of software is a set of instructions that tell a computer how to do something. We use languages like C, Python, Haskell and Lisp to describe what to do in ways that avoid as much ambiguity as possible.
The question is: if you used a natural (human) language, how much confusion would (a) the putative GPT-3000 (b) another programmer (c) a compiler/interpreter incur when trying to understand your instructions?
That might be true. But the comparison is not with another programmer. Rather, it's with the essentially instantaneous understanding that a compiler/interpreter has when you feed in a programming language.
This has been attempted many times in the last century but never happened, I think it's quite safe to say that this also won't happen in the next century.
To be fair, the C compiler also requires a C compiler. Bootstrapping is a common milestone in language development (Rust can bootstrap itself too), so while the current state of affairs for Zig might require a C compiler, that's not necessarily written in stone.
> The ZIG compiler requires a C compiler to compile to a native executable though.
Do you mean compiling the zig compiler or zig programs?
I think neither is quite the case. zig itself can build both Zig programs and C/C++ ones. Zig doesn't need to use a C compiler to build native Zig executables. It does need a linker, as does Rust, as do C/C++ programs and AFAIK zld is not yet ready.
However it's accurate to say that it's only able to compile the C/C++ code by leveraging libclang, the C/C++ compiler underneath.
Just curious. Why do you have doubts about about the package manager? There are so many languages that have a package ecosystem, so by now you'd think its easy to find out what works and what does not.
The build tooling of Zig is very impressive. I'm a sponsor of the project not necessarily because of the language itself, but because it surely has the potential to make waves in some way or another (edit: though the language itself is also quite interesting).
One thing I wish the blog post had brought up is testing! Zig has some great tooling around tests, and that is something sorely lacking in a lot of C projects. If nothing else, you can pretty easily use Zig to simply write some tests for a C project! Even that would be a big improvement to the ecosystem.
Generally speaking, what killed C++/C for me is the absolute insanity that comes with it's build systems/third party dependency management.
I feel like the notoriety that C++ gets for it's absurd complexity comes not from variadic template metaprogramming/rvalue references/unique_ptr or whatever, but the absolutely humongous effort needed to understand automake/CMake/autotools just to build other people's code (god forbid on a different platform than the original author).
I totally 100% agree. "Getting started" with a C/C++ project is a huge pain and IMO where most people get stuck and give up on their weekend project.
I'm working on a game engine in Zig[0], and I've been able to package up GLFW, write a build.zig file that `git clone`s all of the third-party system dependencies so that anyone can just:
And have GLFW building (and cross-compiling!) for their project, without installing anything other than Zig and Git. No XCode. No `apt-get install ...`. Nothing. Just `zig` and `git` binaries. Zig is the C compiler and builds the GLFW source, and I have repositories with the required prebuilt system libs for cross compilation.
I'd say both. C++ is so insanely complicated at times with the various things you mention. And every release it just seems to get worse, because while "nice to have features" are added, the old stuff doesn't go away, and the syntactic gymnastics they have to go through to get it all to coexist are the programming language to who's on first.
But the stack of stuff it takes you supposedly need to get stuff to compile is often insane. On one project I demonstrated that just find . | grep ".c$" | xargs gcc ... and a final link ran significantly faster than the mass of recursive nested/fake/etc makefiles that were supposed to speed up the compiles by doing dependency computations. I remember showing someone the basic "we turn the .c files into .o files, and then we make a second pass to link 'em" and they were like "really? that's all there is to compiling a C program?"
The real straw for me when I was contributing to the Cairo project was recognizing I was going to have understand this stuff more, and so I sat down and read the Make manual: 16 chapters. Now I need to understand Automake, read the manual: 26 chapters. And then Autoconf: 21 chapters. It was like it was just spinning bigger and bigger.
> the absolute insanity that comes with it's build systems/third party dependency management.
Well yes, that would kill me too, if I actually expected the language to come with a single blessed dependency that addresses functionality <Foo>, that I will never want or need to patch the blessed dependency, and that there would be only one way to actually build a program or library.
But it doesn't kill me because I don't expect either of those things.
The dependency stack for my (21 year old) project consists of 86 3rd party libraries that on an x86_64 platform take up about 2GB after compilation. This is intimidating and sometimes painful, but I don't find it insane in any way. We have patches for a half-dozen of the deps that will never make it upstream and we have to be sure that we compile the libraries in the precise way that we need them (for example, we require the thread-safe version of libfftw, not the regular version).
I prefer D, it can be fully compatible with C, the syntax is easy to learn if you've used any of the C family you already know 80%, the package system is simple, modules mean no more headers or include statements just import things where and when you need them, supports every style of programming, QOL improvements like ranges and foreach, proper strings, optional garbage collector, has a long history of continuous improvement and support. To me it's the clear choice if I'm going to rewrite an old C project since I can mostly just copy paste the old code and it runs, after which I can clean it up and simplify it using the new features mentioned. Oh! and integrated unit tests and documentation generation are superb. Helpful error messages too. I could go on but I'd prefer everyone just try it out and see for themselves.
Quoting Walter from the thread linked above on it's purpose/reasons (which I think ought to go somewhere official tbh, since that brief discussion in the above thread is the best + most succinct overview of it currently AFAIK)
The reasons for ImportC are:
1. It's no longer necessary to translate .h files to D to interface with C. While doing the translations isn't hard, it is tedious, and if there are a lot of .h files it becomes a barrier.
2. .h files get updated over time, and correspondingly updating the D files is error prone. It's much more effective to just compile the new .h files.
3. It is not necessary to even create a .h file - just compile the .c file with ImportC and D will access it just like any other module. D will even inline the C functions in it.
4. Use it as a standalone, small and fast, C compiler.
`@cImport` in zig does the same as `ImportC` for D. You import a C file and can then just use it in your program. Notably it existed in Zig before it did in D.
I would say that Zig has the same advantages for a lot of these points. Its interoperability with C at the source and object level is first-class, the syntax was very easy for me to pick up as someone familiar with C/C++, the features it adds on top of C (e.g. slices, compile time execution) are few but huge QoL improvements, its translate-c utility works amazingly well at transpiling C code to Zig, and its test blocks make unit testing trivial to integrate.
Out of curiosity, have you written apps in both languages?
I have written small applications in both.
To be transparent: my total dev hours in D are probably somewhere around ~100, and in Zig about ~30.
So I am more familiar with D than Zig, though competent enough with both to have written a few small real-world programs.
> Its interoperability with C at the source and object level is first-class
D has direct interop with both C, and C++. Also, it's recently gained it's own C compiler, and can natively compile C code so that they are D objects, not just external definitions.
> the syntax was very easy for me to pick up as someone familiar with C/C++
D is closer to C/C++ than Zig IMO. One of it's design goals is actually:
"10. Where D code looks the same as C code, have it either behave the same or issue an error."
And:
"The general look and feel of C/C++ is adopted. It uses the same algebraic syntax, most of the same expression and statement forms, and the general layout."
I found Zig to be much less syntactically similar to C/C++ than D. Zig felt more like a mix of Rust/Go.
> the features it adds on top of C (e.g. slices, compile time execution)
D also has slices and (outside of Lisp) was the first programming language I believe to introduce CTFE and CTFE-based metaprogramming. C++'s implementation as I understand it is essentially lifted from D.
Not trying to start a pissing match, because I actually think that Zig has a lot to offer as well -- but for these particular list of things, there's no clear winner here.
D has a lot to offer. But C absolutely crushes everything else in the embedded space, on micro-controllers, and if a language wants to be a C replacement, it'll have to leave C gasping right there. D quite simply never tried.
Zig on the other hand seems like a silver bullet aimed straight at C's heart, the embedded space. Once it arrives it'll only have to worry about Rust.
Rust is the black hole at the center of the software universe, slowly sucking the blood out of everything else. Zig vs Rust is shaping up to be an epic battle, like the Jedi vs the Empire, for sure :)
Is syntactical similarity or even compatibility to C a positive thing? Not taking away anything, D is a really cool language that definitely doesn’t get the fame it would rightfully deserve, I just think that breaking with some old habits (eg. postfix types with : are simply superior from a parsing perspective, and the pointer magic of C is just as terrible and the change in Zig is very welcome) when they are superseded is a good thing.
I do actually prefer postfix types, so I'm not against you there.
The bit about D is that while you CAN write it to look like C, (or even directly embed ASM blocks in it), it's usually written in a higher-level form without pointers that looks more like JavaScript:
// Count line length of stdin
void main() {
import std.range, std.stdio;
auto sum = 0.0;
auto count = stdin.byLine.tee!(l => sum += l.length).walkLength;
writeln("Average line length: ", count ? sum / count : 0);
}
----
import std.algorithm, std.conv, std.functional, std.math, std.regex, std.stdio;
alias round = pipe!(to!(real), std.math.round, to!(string));
static reFloatingPoint = ctRegex!(`[0-9]+\.[0-9]+`);
// Replace anything that looks like a real number with the rounded equivalent.
void main(){
stdin
.byLine
.map!(l => l.replaceAll!(c => c.hit.round)
(reFloatingPoint))
.each!(it => writeln(it));
}
I am a fan of all the interesting work happening in zig, especially tooling side. But I don't like that headline.
Zig is a system programming language, whereas when someone says general purpose programming language they mean java or python. And "robust, optimal, and reusable software" - this type of words are used by all projects these days that it sounds like MBA speak.
I also don't like the tendency of system language projects (zig, rust) to market as if they are application languages, perhaps in an attempt to drive adoption. Can't blame them for this, but it seems an influx of relatively young, inexperienced programmers from ruby rails / javascript crowds creates false impression of a strong ecosystem.
Agreed for sure that working with C and C++ is the only way forward for systems languages. Rust's expression of this is the zero-cost C FFI, using native platform tooling, and stuff like that. Rust was never about re-writing the world, after all, its reason for existing was to eventually improve Firefox. The very first presentation about Rust (http://venge.net/graydon/talks/intro-talk-2.pdf) says "We are not “rewriting the browser”. That's impossible. Put down the gun."
Zig's cross compilation stuff and general compiler features, that this post talks about, is excellent, awesome, and makes me quite jealous frankly.
Not sure if this transpired in the writing, but my interpretation of RIIR, partially informed by the chat with JT [0], is that this is a sentiment that I guess is there in some people, but that it was never something officially sanctioned.
It is a weird self-fulfilling thing. People talk about it like it's a thing, so it's a thing, even if there's very little actual evidence of anyone sincerely holding this belief. People repeat that there's this plague of folks requesting that projects be re-written, and while it is literally true that I have seen two or three instances of this (you link to one of them, and notably it is not anyone harassing maintainers about their choices), it's been two or three.
I actually got annoyed with this enough that I started doing a quantitative analysis; if you look at the canonical repository tracking this, it's got 44 total issues, most of which are jokes https://github.com/ansuz/RIIR/issues If you search GitHub for issues with these words in it, you get some, but many are either obvious jokes, people making issues on their own projects to think about doing this, and things like that. I never followed through on collecting it into a blog post though.
I didn't think your writing was inappropriate at all; memes are memes, and I'm convinced that this one is just never going to die, because it's taken a life of its own, regardless of the underlying truth or not. It is always worth pushing back on this sentiment, even if I don't think Rust specifically tends to actually embody that sentiment very much.
(Also: I don't know why you're now being downvoted. Hacker News works in mysterious ways.)
As a counterpoint, I remember when someone satirized RIIR on HN and the post was flagged into oblivion even though the comments section was mostly approving.
My impression is that there are more than a few Rust developers who dearly believe in rewriting in Rust and were sincerely, bitterly offended by a joke to the contrary.
See, I see that as the opposite: it is easy to get offended by the joke precisely because it is not true. IMHO, the second version is better because, by removing the language at hand, it directs the joke at the idea of re-writing everything in the first place, rather than at some specific niche of people who may or may not even actually exist.
I could/would also get into a debate about "satire" and what exactly it is and means, but I have complicated feelings about it, how words change over time, etc.
I've found it be to pretty useful in determining why I'm much more sensitive to things I care about than the things I don't, and particularly when jokes carry an element of truth that I cannot bear to acknowledge
Agree with what you said. Another data point I noted how often popular Rust associated people hit Twitter whining about a disagreement they had on hacker news. I have almost never seen with other popular/mainstream programing languages. But for Rust it is regular thing.
Oh I’ve been doing that since before Rust existed. And I assure you, complaining about Hacker News goes beyond Rust folks. Heck, even pg has complained about Hacker News on Twitter.
It is with people who support Rust ( enthusiast ), but not (most) Rust developers. And it isn't just Github, but literally everywhere from Twitter, Reddit, HN to many other social media. That is why you get the upvote / downvote, and where you get the question Why are you not writing / rewriting it with Rust? And it isn't just about Rust itself, all other programming languages topic will always end up having rust in the comments.
As a programmer I think it's notable that when I learned Rust I wanted to rewrite stuff in Rust.
I rewrote 'leakdice' as part of learning Rust, so arguably that doesn't count (though I had never rewritten a program to learn a previous language), and 'misfortunate' wouldn't make sense in unsafe languages so that doesn't count either, but I then also immediately began rewriting 4store in Rust.
I had never been compelled to rewrite stuff in other languages I learned. I am a compulsive programmer, so I did write stuff in languages I learned, (the other day I re-discovered, via an enquiry to a very old email address, that I used to know Scheme, as evidenced by the fact I wrote some Scheme that they were asking about from last century) but I didn't rewrite anything until I learned Rust.
C. Go. Python. Java. PHP. Bash. Perl. C#. Early C++. I'm sure there are many more. But always new things, either because the language afforded something I hadn't considered before or more often I wanted something and I chose a languages I knew would be able to achieve it. That's why I wrote some PHP less than a year ago, I'm not proud of that, but PHP got the job done so that's what I did. I can report that modern PHP is merely a bad idea, like Javascript, which I probably forgot to list above.
Anyway with Rust I not only wanted to write new low-level code in Rust, I looked again at C programs I use and thought, "I should rewrite that in Rust". It felt like a good idea, and I expect to follow through on it at least somewhat, let's see how long 4store takes.
I think it matters less and less, which is part of why I didn't follow through on publishing.
As to why it used to matter, well, it is tremendously difficult to bring a new programming language into popular usage, and even more in the systems space. It requires tireless effort by a large number of people. It is a very fragile thing. The stories we tell ourselves and others matter, and they influence what happens.
I see it more like an aspirational goal. But one where everyone understand that is at most about start new projects that wholesome replace them (or "replace" is for create alternatives, not for truly rewrite the old ones).
And I'm one that will be super-happy if all C/C++ code in the world dissapear now and be replace by Pascal/Rust/Zig/D/anything else that is better.
MOST of my troubles are because C/C++. And the worst ones. Even if I 'm in a environment (business apps, ERps) where in theory system lang concerns are a distant worry.
Since Rust is built on top of LLVM, what's preventing it from "just" adding the Clang C/C++/ObjC frontends into the Rust compiler, and create cargo packages with cross-platform C/C++/ObjC headers and runtimes? This would make cross-compilation and C/C++/ObjC integration just as easy as with Zig (and without requiring any external C toolchain, which is the most important point).
I guess the other (simpler) alternative is to wrap Zig into a Cargo package, similar to this:
Technically? Nothing that I'm aware of. Practically? The desire to do it, and the engineering work required to make it happen.
I have been talking about how much I would love this to happen, but I can't do the work myself. Frankly, I don't contribute to the compiler, so even getting up to speed would be a ton of work I just don't have the time for.
Hilariously it was a copy/paste situation that I went "oh, that's kinda neat, I'll leave that" so I'm glad you noticed. (I am often guilty of nesting thoughts (inside of other thoughts) which is kind of amusing (even though I've never found lisps particularly easy to read)).
The boat has already sailed, but it's GCd languages and not Rust that replaced most usage of C and C++ in systems programming. Rust is nibbling at the remaining niche islands of C/C++.
Rust isn't only nibbling at the C/C++ niches: thanks to its correctness and productivity aspects it also attracts people coming for example from Go/Python/Javascript.
In some way, you could say that Zig is pulling system programmers towards high-level programming, whereas Rust is pulling high-level programmers towards system programming. That's not a watertight comparison but I think it's an insightful one.
If zig.build can alleviate the pain of building C/C++ projects, then it will surely to replace C in the future. So, IMHO, it's more important to get zig.build right than zig itself
Aren't C projects relatively easy to build? C++ is a little more complicated, so there is more of a push for header only libraries there. I see the problem is probably with external libraries, but that is largely not a huge problem on Linux/Unix. Windows was more of a pain but msys/mingw alleviated most of it.
not really, unless you're only building for one platform. gnu make isn't portable and doesn't have any real dependency management, so meta build systems quickly become a necessity. cmake is the most portable and the only real option on windows, but it's common to see projects that maintain support for both cmake and autotools.
and that's ignoring the nightmare of cross-compiling with gcc.
andrew kelley has talked about this[1]. his main point was about the number of dependencies you end up with compared to zig, where the standard library includes a high-level build system api. so your zig build scripts are written in ziglang and run by the zig compiler. no extra dependencies.
Surely if you ensure people can't write macros in your language they'll just wrap the entire language in a pre-processing layer to enable macros which seems obviously worse ?
I disagree for two reasons. The first is that if the traditional upstream stops caring about improvement, it's only reasonable for other parties to move on. And that's largely true with the C ecosystem, as it was with OpenOffice for example.
Second, "extend" has traditionally meant "extend with proprietary code". Think early 2000s Microsoft with browser APIs and J++, or Amazon with their in-house forks of MongoDB, PostgresQL, etc.
There's plenty of innocuous activity that could be called E3 if you stretch the definition that far. Most of the GNU ecosystem for example.
Interesting read and I agree that growth in technical debt is something to worry about. I want to get more involved and been trying my best to learn the skills needed but there’s so much to consider that it’s a bit overwhelming. I’m not familiar with Zig, it’s neat that it has a toolchain that can compile C/C++ but it reminds me a lot of Nim. Could someone explain to me some differences between the two and why you would pick Zig over the other besides personal preference?
I'd say Zig wants is really serious on wanting to be a better C, that is have the same (better?) access to bare-metal, memory and bits but with a better type system and error management and without C's footguns and cruft (build system, memory errors, preprocessor).
I don't know Nim that well but it seems more ambitious and seems to cater to be more "general purpose" if that means anything. For example there's no hidden flow control in Zig.
I think the maintainers of Nim would disagree with your assessment given that the first sentence on the front page of Nim's official website is:
"Nim is a statically typed compiled systems programming language."
As someone else pointed out, when using Nim with its ARC/ORC and move semantics (which will eventually be the default), it's closer to Rust than to Go.
That being said, Nim's current default GC does well for many kinds of workloads. If it's really causing a performance problem, it's possible to disable it and manage memory manually (or use a different language for the task at hand, of course).
Interesting; this piece advocates for porting only (or at least initially) the build system of C/C++ projects to use the Zig compiler and the build.zig build system for cross-compiling dependencies. A package manager in the works could be interesting too.
I don't do C/C++ development. How many headaches would using Zigs compiler & build system solve?
In principle, why wouldn't it work for C++/WinRT, given that it doesn't use the WinMD metadata directly, but rather produces valid C++ headers from it?
I think the problem for Zig is that they use MinGW as their Windows target, and the latter's standard library isn't really UWP compatible (yet?).
Because the whole build process to produce a MSIX or APPX requires a little more, like the whole Windows SDK, plus whatever might be fetch via NuGET via MSBuild, and the process to sign the packages?
I would compare building MSIX or APPX to building an apt or rpm package on Linux - that's the next step after building the actual executables, which is what Zig covers.
The part that Zig does well, is that it bundles C headers and standard library sources with it. This is the hard part of cross compilation. The default install can build standalone executables from C programs for any supported platform.
If someone wanted to package clang to do that, they could. They do, in fact, that's how clang cross compilation packages work.
But sure, to Zig's credit, they ship all the supported toolchains and so don't have many different cross compilation variants in the OS's package manager. But that's a bit like buying one of everything, even though you'll only use one or two.
> If someone wanted to package clang to do that, they could. They do, in fact, that's how clang cross compilation packages work.
Clang doesn't bundle a C standard library implementation.
> But sure, to Zig's credit, they ship all the supported toolchains and so don't have many different cross compilation variants in the OS's package manager. But that's a bit like buying one of everything, even though you'll only use one or two.
Zig only bundles the sources, so it builds the C library on-demand. IIRC it's smaller than most cross compiler toolchains.
> Which it then caches, post-compilation, taking up binary space on disk comparable to if you just downloaded the pre-compiled target binaries.
I don't understand the point of this comeback. If you have the artifact cached it means that you compiled for that target, while the rest of the stdlibs remain in source form (also Zig deduplicates header files which is why everything fits in a 40mb tarball), and you didn't have to download anything manually. What else would you want exactly?
What I don’t understand is why Clang doesn’t do this? What’s stopping them from shipping libc’s for cross compilation? Given that they are a C and C++ compiler, surely this is in their domain.
In principle, clang could do it, you're right. I think there are two main reasons why it hasn't.
First, Zig has a pretty unique and impressive build&caching system. It makes something like this a lot less painful than it would be otherwise. It makes it practical to just download a 40MB tarball and get full cross-compiling from it.
Second, clang itself isn't in the business of building a distribution of itself + third-party libraries. Usually developers using clang get such libraries from their "vendor" (package manager if on a Linux distro, Apple if on MacOS, etc.). And those vendors naturally focus on their own platforms. So Zig ended up the first to really do the work to build a cross-platform C/C++ toolchain.
You need to provide low-level build functions, which is quite some work.
On top of that you need to understand the build system of the libcs to replicate it.
On top of that for libcs you want to deduplicate all symbols to reduce as much space as possible.
On top of that you might want to provide glibc symbol versioning.
People without incentive usually do not do this tedious kind of work, since "it works for me".
> once you learn JS you can do [...] video games (Unity)
Not true. Unity once supported their own language based on Boo, a python dialect for the .net runtime, that they falsely advertised as "Javascript". That has since been depreciated.
‘zig cc’ is more than that. You get a very powerful caching system, and built in support for cross compilation to a huge range of targets (via zig shipping with libc sources). Zig is extremely impressive.
The built in cross compilation is just LLVM; it's a narrower set of targets than GCC, and a _far_ narrower set of targets than where you'll find a C compiler.
clang cross compilation is not really any harder, you just supply the -target flag.
It can, if the package manager bundles it that way. Instead, clang is usually packaged and distributed as cross-compilation variants; because most of the time users don't need all of the different possible targets, they just want one or two.
Quoting a reply about this point I left in another comment thread:
The point about the Python package example is not to say that Zig can get on platforms where Rust can't, but rather that the C infrastructure that we all use is not that easy to replace and every time you touch something, regardless of how decrepit and broken it might have been, you will irritate and break someone else's use case, which can be a necessary evil sometimes but not always.
This is a point I made in the section of the blog post dedicated to explaining the limits or rewriting code.
Yes, if you exclusively use Zig to build the code. But on an platform that Zig doesn’t support, you could write your own makefile instead to compile the C code with the native compiler, so the situation seems a bit better than with Rust?
llvm’s platform support or the lack thereof was not the issue, it was assuming you had a compiler other than a C compiler installed at all on certain platforms. This would have affected zig in the same way, or any non-C language.
The author seems to be under the impression that Rust cannot live harmoniously within the C ABI ecosystem, which is false.
To some extent. Rust, due to relying on LLVM, is limited by its available targets; and so cannot enjoy the full access that C/C++ can. The same is true for Zig, however, since it does not compile to C.
> The author seems to be under the impression that Rust cannot live harmoniously within the C ABI ecosystem, which is false.
The author is not under any such impression. The key point is that Zig can cross-compile C/C++ code, while Rust can't. If you have a Rust project that depends on C code, you will have to put in some work if you want to compile it for a different target.
That's a pretty big difference in practice. The point about the Python package example is not to say that Zig can get on platforms where Rust can't, but rather that the C infrastructure that we all use is not that easy to replace and every time you touch something, regardless of how decrepit and broken it might have been, you will irritate and break someone else's use case, which can be a necessary evil sometimes but not always.
This is a point I made in the section of the blog post dedicated to explaining the limits or rewriting code.
> You're splitting hairs in a weird way. rustc cannot compile C code. zig can.
But why do I care? I don't use rustc directly, the build system of choice does. And very few of the major build systems have an issue handling multiple languages.
Cargo (rust's build system) supports build scripts and the community has already created C/C++ compiler hooks such as https://github.com/alexcrichton/cc-rs
rustup and cargo also provide easy cross-compilation support, too.
You care because you need to have a c compiler installed, as well as the libc stuff… It is a giant pain to do so in some circumstances. One less dependency is a good thing.
100% of platforms that Zig & Rust run on already have a C compiler installed, though. A c compiler being present is the baseline assumption. If Zig existed in places C doesn't and I wanted to write C for that platform then having Zig CC would be an advantage. But such a situation doesn't currently exist and seems unlikely to ever exist? Especially in the context of a library/module porting to Zig or Rust piecemeal, though, then I already obviously have a working C compiler & build toolchain in place since that's my start point.
You might not be aware that Zig runs perfectly fine on Microsoft Windows, without MSVC installed. Consider also that installing Zig on Windows is a matter of unpacking a 40 MiB .zip file, whereas installing MSVC on Windows requires downloading something like 8 GiB, being an administrator (!!), and restarting the computer twice.
you may have one. Not everyone does, or should. It is a massive pain. See how many folks complain about various native wrapper packages in Python and Ruby for example. Someone even made a Python wheel wrapper for zig to make it easier to compile C extensions.
Yes, most of my systems have "zig cc" as a C compiler installed nowadays. Why? It's so much easier to install. Especially on systems like windows, and i get other benefits. One example: "I can send you a project, and you can just compile it." Dependencies? "Zig toolchain, any OS"
I haven't had this experience with any other language. When a package manager lands, you won't even have to install any SDKs by hand anymore.
Yeah it was a really odd choice of a "why not Rust" bug since the problem with Rust in that bug applies entirely to Zig as well.
So if you can't use Rust because it doesn't have the broad compiler support that C does, you almost certainly can't use Zig either for the exact same reason. And then the article touts Zig using their own in-house linker, which seems inevitable to have less platform support than LLVM will? Zig isn't wrong for doing that, but you can't claim that's a benefit at the same time the problem with Rust is it's lack of fringe system support... :/
> which seems inevitable to have less platform support than LLVM will?
ZLD can link for M1 while LLVM can't yet. And Zig can also use lld alongside it to increase the platform coverage.
> but you can't claim that's a benefit at the same time the problem with Rust is it's lack of fringe system support... :/
I find it hard to believe that a dispassionate reader could misinterpret to this degree the goal of a sentence written in a chapter dedicated entirely to showcase the limits of rewriting code in another language, be it RIIR or RIIZ.
See my reply to the parent comment for the intended meaning of that example.
It's not just a random remark, the self-hosted implementation of the compiler which is being worked on as we speak (type?) has multiple backends, one being the C backend which will be used to simplify the bootstrap process of Zig.
Agreed, and Rust folks have been working on a gcc backend to get off the llvm monoculture too.
As much as I lean on thinking Zig will achieve it first it seems unfair to make claims about things that haven't been done in a language that hasn't even been finalized as reasons it's better than a stable language in the same boat at the moment.
Vlang started as a closed source project with a lot of lofty claims that fell apart quickly once the project was actually opened up. Combined with all the delays and broken delivery promises leading up to that, it gave people a poor impression of the project.
With that said, I've heard the situation has improved since then.
Similarly almost every feature in V has some hacks or doesn't work outright. But authors of V don't have enough dignity to mention the in-progress/missing features on home page. And when somebody points them to their dishonesty, they block them on discord.
I see you created this account to spread false information about V:
> Ditto same experience, I asked if somebody could explain me the memory model of V; and all of sudden hell broke loose. I was asked why do I need to know how memory model worked, are you a troll, if you don't contribute to the lang you're not welcome here. Never asked a second question again.
This never happened. Otherwise you could send a link to the discord conversation.
No one gets banned or called a troll for asking questions.
Besides, the memory model is explained in details on the home page.
I too am surprised V does not get more attention here. I think the main developer frayed his ties with HN by some of his early claims. Nonetheless, he's doing good work moving V forward and developing community. [0]
> To improve our critical infrastructure we must improve the developer experience (DX) of systems programming, but rewriting everything is not the only answer.
I like Kristoff from what I've read of him. He seems like a leader who will take responsibility and not shy away from hard decisions.
On that quality alone, my money is on Zig winning systems programming over Rust in 10 years.
You can build C projects using Rust and Cargo as well[0], but it requires more work than Zig appears to need.
Rust also has good C FFI story and honestly I think we will still see a lot of important code rewritten in Rust.
That said, a more ergonomic C which seamlessly replaces the often bloated build configuration of C/C++ projects while allowing a far nicer language to slowly replace the existing code feels like TypeScript for JS (which I consider a good and smart thing). Definitely agree it's going to get far more use than I'd originally have guessed.
When choosing between Zig and Rust for systems programming, I would reach for Rust most of the time. It's just a more expressive language with some seriously awesome compile-time guarantees. There are three things that bother me about Rust: bloated binaries, slow compile times, and dependency bloat (accidentally building 5 versions of the same crate). But seeing as it's being integrated into the Linux Kernel, I think Rust has a long and bright future in that space. Having said that, I think Zig would be a really great language for kernel programming.
In the 21st century, if you're building systems infrastructure and are able to choose a language that's not C or C++, it's a pretty big call to decline memory/type/datarace safety.
You replace the pipeline from GCC/LLVM to Zig. But the codebase is still in C and therefore has all the negative properties (memory unsafety, undefined behaviour), so what did you gain?
LLD is written in C++ and depends on LLVM (a massive C++ library). Zig's self-hosted linker has no C++ or LLVM dependency. One of the features of a compiler is how short the bootstrapping path is. C++ as part of the bootstrapping process is a failure of sorts.
Furthermore, LLD doesn't support incremental compilation. It's just not fast enough for our compilation performance goals.
I'm not close to Zig development, but in addition to the GeneralPurposeAllocator work that a comment already links to, there is other ongoing stuff in that space, like an allocator in 64-bit that does not reuse pages (and instead keeps using new addresses in the "infinite" 64-bit space).
There are also known techniques like Type-After-Type that can avoid use-after free in C and C++ [0]. And also advances in hardware like ARM MTE [1]. Those things - and GPA, and not reusing 64-bit pages - have various tradeoffs in terms of overhead, but Zig is a developing language that has a chance to explore possibilities that C and C++ can't. So this is an interesting area to follow.
Seriously. You get these tests for free with zig, no extra tooling, if you use testing allocator in your tests. This also is a carrot to get you to write tests. Hell, I even mocked libc's malloc/calloc/free to make sure that my code doesn't leak memory:
Rust does not free you from having to track memory, for large classes of important data structures. And, if you want to implement smart pointers for your app in zig, it is easy.
Yes, your examples are two proprietary, nonstandard techniques. Or alternatively using a third-class toolset (jemalloc toolkit e.g ).
Sanity is having a single, anointed way to do it in the stdlib.
Any plans to go one step further towards memory safety (and even better data race freedom)? Maybe using ARC when the overhead is acceptable (like Swift), it static analysis is not possible due to the type system not supporting the concepts of ownership, borrowing, lifetimes and so on?
Zig is super exciting, but without a GC or Rust type system, I’m worried about going “back” to a language where I have to think about memory leaks, use after free and so on.
As of now the tradeoff of Rust's type system is that code may leak memory and you may need to investigate all occurrences to fix it, because there is no tooling yet (testing everything is infeasible).
You can have either extensive static analysis or fast compile times. Tracking ownership down to semantic analysis (where type layouts are in resolved form) requires adjusting and slowing down the complete compiler.
"It's intentionally less safe! That's why you should use it!"
Zig may strike a better balance than Rust does here, but it seems more like Rust stole its lunch. I don't know why I'd migrate from C/C++ to Zig instead of going all the way to Rust?
1. Rust doesnt let you safety-check bit-compressed stuff and instead relies on well-formed types during runtime (basically everything that union allows).
2. Graph memory patterns in Rust are completely unsafe.
3. Code might leak and you dont have tooling to check your dependencies. Trusting the test coverage does not help you there with Rust, because Rust tooling is infeasible for testing leaks.
4. CTFE is not easy to track in Rust, so you dont know if certain code may have run-time cost or not.
1. Unclear what you mean. You can manipulate bits of scalars without writing unsafe code. You can't do bit manipulation of pointers without writing unsafe code. You can write a small library that packs pointers with unsafe code, and then use it safely everywhere you need it. See https://github.com/rpjohnst/dejavu/blob/master/gml/src/vm/va... for example.
2. Wrap up the unsafety in a library like petgraph and use it. Or just use petgraph.
3. jemalloc's leak analysis works with Rust, as does Heaptrack.
4. With const generics it's trivial to pass a value through a const generic parameter to ensure it was evaluated at compile time. Right now that only works for scalars but that limitation will be relaxed later.
5. Most Rust code is safe code which is free from UB. Dynamic checks of all unsafe-code UB is infeasible. So what?
2. petgraph looks like they only allow allocating and freeing in the same order and no deallocating of segments (think of a graph pointing to another graph) dynamically. So even if you wrap it, you cant arbitrarily deallocate and allocate things upon semantic conditions.
3. It would be more helpful to use static analysis or making functionality with potential leaks similar to have this information on code review instead of trying to test all code paths.
4. True. I think that works then. Looks like between Rust and Zig "const equals comptime".
5. "Dynamic checks of all unsafe-code UB is infeasible." It is only infeasible, if you dont have an idea on the memory model during comptime. The RFC also writes "In particular, this would mean we need to decide on an aliasing model before permitting raw pointers in CTFE.", because Rust is not settled on that yet. On the upside, this speeds up compilation, lol.
1. f32::from_bits/to_bits etc allow safe bit-casting between primitive types. Libraries like safe-transmute wrap up unsafe array pointer casts into safe APIs. Rust has great tools to address the use-cases where C would use unions.
2. The big picture here is that for graphs and other data structures, you figure out a safe API for the data structure and write a library that hides the unsafety behind that API (or just use a library that someone else already wrote). You get a clean separation between a small amount of unsafe code and a large amount of safe code. This is the Zen of Rust and it's why "Rust can't express <XYZ> safely" is almost never an important issue in practice.
3. Rust's ownership and determinstic destructors mean you really have to go out of your way to create a memory leak; "just forgot to free it" doesn't happen. A memory leak has to be something nontrivial like a global data structure that is added to but not removed from, or a reference-count cycle that isn't broken. There aren't really good automatic static techniques for detecting these AFAIK (and I have a background in static program analysis) --- you would need something that models heap states over time, like shape analysis, which doesn't scale. As far as I can tell Rust at least as strong as any other real-world non-GC language at preventing memory leaks, certainly stronger than Zig which lacks ownership and destructors.
5. This is an issue I don't know much about so I'll leave it.
FWIW the endgame for unsafe code in Rust is formal semantics for unsafe code, proof obligations that guarantee the unsafe code preserves Rust safety invariants, and proof-assistant tools that let you formally prove the safety of your "unsafe" code. There's a lot of work to get there but that work is well underway.
You wouldn't because you already know Rust. The mental overhead of Zig is essentially zero if you already know C. Also, generics and other syntax is cleaner in my opinion, but that doesn't matter that much.
> soon we’ll also have a package manager
This. If they succeed to do this (and I have my doubts) it will be a paradigm shift for low-level "system" programming.
Let's hope this is not vaporware.
Finally, it's relatively easy to contribute to Zig. Andrew Kelley is very opinionated on where the language should go with an emphasis on simplicity which sometimes can make things awkward but he is also persuasive and welcoming. I have been using Zig for a month and I am still positively surprised so he must be onto something.
[0]: https://github.com/ziglang/zig/blob/master/lib/std/json.zig#...