This is a huge advantage over C++ and Rust, because it makes it much harder for e.g. the intern to write code that repeatedly creates a vector or dynamically allocated string in a loop. Or to use something like std::unordered_map or std::deque that allocates wantonly.
Right now you could also go no_std (you still get the core library, which does not contain any allocating data structures) and use custom containers with a passed in allocator.
Zig is definitely a cool language, and it will be interesting if they can come up with good solutions to memory management!
But sentences like these in the documentation [2] would make me prefer Rust for most low level domains (for now):
> It is the Zig programmer's responsibility to ensure that a pointer is not accessed when the memory pointed to is no longer available. Note that a slice is a form of pointer, in that it references other memory. ...
> ... the documentation for the function should explain who "owns" the pointer
[1] https://github.com/rust-lang/wg-allocators
[2] https://ziglang.org/documentation/master/#toc-Lifetime-and-O...
While such use-after-free issues are not prevented at compile time, the plan is to ultimately have safe Zig catch them (and panic) at runtime, i.e. safe Zig shouldn't have undefined behaviour. Because this is done with runtime checks, the expectation is that those checks will be turned off (selectively, perhaps) in production, after testing has satisfied you. In that case, the guarantees aren't as strong as Rusts, but those guarantees come at a significant cost -- to language complexity and compilation time -- that can also have a negative effect on correctness. So while Zig's approach to safety/correctness is certainly very different from Rust's, I don't think it is necessarily weaker (perhaps it could even be stronger, but the question is hard to settle).
If you want a full blown container you can use heapless, building a custom container is really straightforward and requires a minimal amount of unsafe.
The irony.
Per container allocators are on the roadmap : https://github.com/rust-lang/rfcs/blob/master/text/1398-kind...
With linear types you can guarantee that a destructor is run, so you can create objects with lifetimes that are not only bounded from below, but bounded by above. There are some common patterns in e.g. C libraries that rely on this--for example, you might register a callback on an object, and you want to assert that the object's lifetime is shorter than the callback's (so the callback is never called outside its lifetime).
Since Rust doesn't have linear types, you have to use unsafe{}.
It'd be pretty unusual for junior or non-technical people to write code in "core" components of the system. Things like datafeed parsers, order handlers, inventory management, safety checks, networking libraries, exchange connections, etc.
But even with all these layers in place, you still need an actual strategy to run at the end of the day. Everything in the quoter can be optimized to hell, but if the strategy module is spinning for 1000+ microseconds because it's running some bloated ML model, then none of that really matters.
Usually the system engineers and the strategists are different people. Not always. Especially in the case of more straight-forward mechanical strategies. But anything reasonably complex usually requires dedicated quants with different skillsets than profiling C code.
That's not to say you're safe to call other syscalls, many of them either require memory allocations in-kernel (see ENOMEM) or can block indefinitely.
True. On the other hand, Zig makes a deliberate decision not to bother itself with memory safety too much, so its a win some, loose some sort of situation.
This is not true. Zig places a strong emphasis on memory safety, it just does so in a way that's very different from either Java's or Rust's. I wrote more about this here: https://news.ycombinator.com/item?id=24293329
In particular, it has two truly remarkable features that no other well-known low-level language -- C, C++, Ada, and Rust -- have or can ever have: lack of macros and lack of generics (and the associated concepts/typeclasses) [1]. These are very important features because they have a big impact on language complexity. Despite these features, Zig can do virtually everything those languages do with macros [2] and/or generics (including concepts/typeclasses), and with the same level of compile-time type safety and performance: their uses become natural applications of Zig's "superfeature" -- comptime.
Other languages -- like Nim, D, C++ and Rust also have a feature similar to Zig's comptime or are gradually getting there -- but what Zig noticed was that this simple feature makes several other complex and/or potentially harmful features redundant. Antoine de Saint-Exupery said that "perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away." I think that Zig, like Scheme -- and yes, there are others -- is close to that minimalist vision of perfection.
What a truly inspiring language. Rather than asking how we could make C++'s general philosophy work better as another increasingly famous language does, IMO, it asks how we could reshape low-level programming in a way that's a more radical break with the past. I think it's a better question to ask. Now all there's left to hope for is that Zig gets to 1.0 and gains some traction. I, for one, would love to find a suitable alternative to C++, and I believe Zig is the first language that could achieve that in a way that suits my particular taste.
[1]: I guess C has the second feature, but it loses both expressivity and performance because of it.
[2]: Without the less desirable things people can do with macros.
That being said, Zig's comptime is not a proper replacement for typeclasses/traits. Zig can do both comptime duck typing and vtable-based dispatch, but it cannot do proper bounded polymorphism type checking. It always fully evaluates types before type checking them. This might make it difficult (or impossible?) to provide type checking error messages of similar quality to Rust. I'm not sure if there are any other practical consequences for realistic programs, though. I suspect there might be issues around interface stability gaurantees, though I can't quite put my finger on why.
Having said that, I do support a proposal for specifying a type at the parameter declaration with some type -> bool function.
I like this quote by Leslie Lamport about comparing formalisms (he talks about specification languages rather than programming languages, but the sentiment is the same):
> Comparisons between radically different formalisms tend to cause a great deal of confusion. Proponents of formalism A often claim that formalism B is inadequate because concepts that are fundamental to specifications written with A cannot be expressed with B. Such arguments are misleading. The purpose of a formalism is not to express specifications written in another formalism, but to specify some aspects of some class of computer systems. Specifications of the same system written with two different formalisms are likely to be formally incomparable… Arguments that compare formalisms directly, without considering how those formalisms are used to specify actual systems, are useless.
I'm curious where this impression of Zig comes from, as this is precisely what Nim has set out to do: a small core extensible via metaprogramming. Are there features that Nim implements which go against this premise? if so, what are they? :)
Nim has generics (plus concepts), templates, and macros. Zig has just comptime, through which it achieves all those goals (minus some macro capabilities that it deems harmful anyway) with just one, very simple, cohesive construct. You could argue on whether you like this or not, but you can't argue that Zig's approach isn't fundamentally more minimal. Zig is a language you can reasonably fully learn in one day; I don't think you could say the same about Nim.
---
Also, note another remarkable feature. One could define a language called Zig' with the following properties:
1. Every well-formed Zig program is a well-formed Zig' program (i.e. Zig' accepts all Zig programs, potentially more).
2. Every Zig program has the same semantics as the identical program when interpreted in Zig'.
This means that to analyse the semantics of a Zig program you can pretend it's a Zig' program; in fact, you don't need to create an interpreter for Zig', you can just pretend it exists. Why would you want to do that? Because Zig' is simpler. How? Here's the kicker: Zig' ignores comptime completely. It is a very simple, optionally-typed, dynamic language with reflection.
In other words, to analyse the semantics of a Zig program you can forget about comptime and pretend it can do everything at runtime (and treat comptime as a pure semantics-preserving optimisation).
This is not true for languages with macros, as they are not "erasable".
It's indeed an inspiring language, and rust is taking inspiration from it already: https://github.com/jswrenn/project-safe-transmute/blob/rfc/r...
> lack of generics
I can't wait before Zig2 comes and eventually adds generics…
But C++/Rust can never have Zig's primary feature -- simplicity. Zig's power is not that it has comptime, but that it has little else.
> I can't wait before Zig2 comes and eventually adds generics…
No need. Zig gives you the same capabilities as generics do, only through a separate feature that other languages also have in addition to generics. In other words, it has generics, but without having generics as a special construct. Zig recognises that once you have that other feature (compile-time introspection) you don't need generics as a separate construct, but they can be just an instance of that single construct.
Edit: clearly gp was not mistaken, just imprecise.
https://ziglang.org/documentation/master/#Introducing-the-Co...
And if you see the flack that go gets for not including generics then I'm not so sure that that is a great way to get people to adopt your language.
I think that our community's equivalent of "where's my flying car?" is "where's my higher-level language?"
I work in a multi-million-line codebase, a significant majority of which is very far from that "need perfect control" domain but is written in a what I'd call a mid-level language - a high-abstraction dialect of C++. So I'd say GP is correct, that too many people are stuck writing code in the wrong language for the task at hand. The need for languages like Zig to improve the lower-level experience (which, as you say, is not going away) and the need for higher-level languages for more common types of programs are not in conflict. They're complementary. It's the mid-level languages that need to DIAF, because they're not really suited for either and pretending to be more general than they are only encourages people to make choices that hurt them.
Burroughs, Mesa, Modula, Oberon, Interlisp-D,... were on the right path, but in technology not always the best ideas win.
Ηelicopters are flying cars and they are everywhere for you to use. But some people prefer to use a bicycle to commute to work, rather than an helicopter. I'd even say that most people would prefer to take a bicycle every day than an helicopter.
The same thing with lower level languages. Sometimes you do not want to be burdened by the limitations of a "high-level" language.
I see very few people suffering from such burdens, but a great many suffering from its exact opposite: using a low- or mid-level language to write hundreds of lines where ten lines in a higher-level language would suffice and be more easily verified as correct.
I think Zig has the potential to be the best of them, fwiw.
Give it a few years and everybody is going to benefit from better implemented "lower-level" applications, thanks to Zig and other languages attempting to do the same.
I've spent much of my career writing low-level code in low-level languages because I had to, usually in C and usually because I was in resource-constrained environments where tight control over CPU and memory footprints was necessary. There's absolutely room for languages that improve programmers' lives in that kind of environment while remaining suitable to that purpose. I'd put Zig in that category, and I find much to admire in it.
However, outside that domain, once you have even a little freedom from those constraints, it makes no sense to use a language designed around them. When even something as simple as manipulating a few strings or updating objects in a map/hash/dictionary requires careful attention to avoid memory leaks or excessive copying, and your code is doing those things a lot, you're using the wrong language. A language that "protects" you by guiding you toward adding the right boilerplate in the right places honestly isn't much of a help. Most code should be written in a truly higher level language, where things like circular references don't require much discussion except by the language implementors. The problem of how to do that without going full-GC and having to deal with pauses is where people should focus their attention, not more languages that just change which ceremony you must adhere to.
Low-level languages encourage the use of clean ownership patterns, which ultimately leads to cleaner design.
Particularly great is Zig's handling of both macros and generic types, the answer to both of which seems to be: just evaluate them at compile-time with regular functions, no special DSL or extra syntax. Andrew mentions in the video a big drawback of this system - implications for IDE complexity and performance. I imagine the performance side of this could be (maybe is?) mitigated by limiting recursion depth/loop counts for compile-time work.
I'm not particularly interested in taking on a language with manual memory management and the responsibilities it entails, but I would love to have access to Zig's compile-time capabilities, if it were available with some more memory safety.
My experience is that Rust's approach is definitely better in terms of correctness than using valgrind during testing.
My intuition is that the advantages Zig brings to the table will not tip the balance.
That being said, the choice Zig makes is absolutely the right one. Rust fills the niche of a better and more correct C++ without fixing the issues of slow compilation and language complexity.
Zig fixes so much of what's wrong with C without abandoning the advantages of language simplicity and locality of reasoning. I love Zig, but need a medium sized non-work project for it.
https://github.com/ziglang/zig/pull/5998
edit: For more details, see
This sounds like "we can't guarantee the most important thing, so it's unclear whether it's useful to guarantee this other thing," but that's a bizarre statement, so am I misinterpreting?
- It's a very low level language. Having written mostly Python for the past few years, it is quite the contrast. I had to force myself to think in C to get the train going
- Getting my head around the error handling took more time than I'm willing to admit. In the end, it's like having exception but being more explicit about it. It feels nice when you get the hang of it
- The documentation of the standard library is severly lacking, to be fair the language is still very young. More worrysome, it feels very cluncky.
- No proper string support. It is sad that a modern language still goes down that route after Python has shown that correcting this is both definitely worthwhile and a word of pain.
- I have the feeling that optional and error union types are a bit redundant, but I have not written enough Zig to have a real intuition on that. Maybe it is just that I understand monads now.
Optional and ErrorUnion are a tiny bit redundant in that one could represent the Optional as another value in an ErrorUnion, and that might even happen as an optimization step in the case of ?!/!? types at some point in the future, but they have very different handling in the language as they are used for very different things.
I personally like that Zig doesn't bother with "strings" at a language level at all and just considers everything as arrays of bytes. String handling is a complexity nightmare and I feel that Zig wisely chooses to be simple instead.
I have not expressed myself clearly: the auto-generated documentation is severly lacking. The API of the standard library is clunky. To be fair, both those points are getting better. And yes, the language is very young and I understand that there are more pressing issues with the core language itself.
> I personally like that Zig doesn't bother with "strings" at a language level at all and just considers everything as arrays of bytes. String handling is a complexity nightmare and I feel that Zig wisely chooses to be simple instead.
It is definitely simpler, alas not everything is ASCII and arguing it should be to make life easy for programers is hardly a reasonable stance.
Also, maybe it is not clear in my comments but I actually enjoy Zig.
Now you may argue that this can be handled nicely in the standard library without changing the language. This is correct, but there will be some frictions with string litterals.
The good thing is that mastering one of these languages gives you conceptual tools which help with becoming at least competent in the others, if not mastering them as well.
But in general I have gone for for generalist not specialist in my career.
I can't help but feel like in our industry C is successful (vs its 80s competition of Pascal/Modula-2, or Ada etc.) partially because of some of the same reasons that Git is successful now. Yes, it is powerful and flexible; but also in some ways unnecessarily arcane and 'dangerous' and _this gives the user a feeling of cleverness_ that is seductive to software engineers.
Put another way: Most of us enjoy the mental stimulation of programming, and we enjoy the mental challenges (in general). C makes us feel clever. Witness the "obfuscated C programming contest" etc.
Same thing that has led to nonsense 'brain teaser' whiteboard-algorithm tests at job interviews. IMHO it's in many cases for the benefit of the interviewer's ego, not the company or the interviewee ("gotcha! no job for you!").
</>
Don't think Zig is worth it when it doesn't have linear or affine types.
After that you should already be in great shape and you can read the standard library for examples of useful patterns.
Semantic Analysis [533/803] ./main.zig:4:14: error: container 'std.debug' has no member called 'print' std.debug.print("Hello, {}!\n", .{"World"});
The Rustaceans should be taking notes on this.
Ans also, that's the strength of C and its simple ABI.