Go is only like C in that it's simple. But it's clearly not a direct C replacement. It's not a great language for writing an OS or embedded code.
Rust may replace C in many contexts, but Rust is much more like C++. It adds a lot of complexity to the language and the compiler. I think it does it in a much better way than C++ so I think it is more likely to replace C in certain contexts where C++ did not replace C.
But Zig really is a much more direct replacement for C. It's aiming at the same niche, but in a different way. And considering how much C still dominates systems programming, it should be pretty clear that if Zig succeeds in become a good replacement, the answer to your question is: you'd reach for it whenever you'd have reached for C before.
The only question at this time is if Rust will eat so much into that niche that what's left for Zig becomes too small. Though it could go the other way. Zig is simpler and easier, and you can iterate really quickly with it. There's even incremental compilation now. I suspect tests with debug allocators, the integrated fuzzing, static analysis and LLM assisted reviews, can make Zig just as safe as Rust in practice, with far less complexity. So maybe Zig will eat into Rusts niche (whenever Zig is mature, right now it's going the other way as seen with Bun)
That said, leafing through the first chapters of "Expert C Programming" should dissuade anyone of the idea that C is a simple language. It'll leave you amazed anyone's been able to write working programs in it
What footguns are present in C bit not in C++?
C++ has all the footguns from C and adds multiple more. C++ is not a replacement for C, in the same way that a spacecraft is not a replacement for a Cessna.
Theyre both complicated languages in their own way :)
That requires the programmer to practice discipline. If someone needs something better than C, there's alternatives to C++ that don't require "Programmer needs to be disciplined".
> Theyre both complicated languages in their own way :)
No. C++ is, without doubt, the most complicated language there is. Nothing else, not Java, C#, Rust, etc comes even close.
C, OTOH, is simple enough that implementing it is practical even for students. The number of footguns are a handful.
They are severe, but not numerous. C++ has both numerous and severe footguns.
I think there still a long way to go before the final version of Zig. I am increasingly thinking 0.17 and 0.18 won't even be close to RC. But I really like the direction that Zig is solving a lot of problems not with language features but with its tool chains. Something I thought was obvious and should have been what it is in the first place. Instead we go increasingly big and complex language.
I'm the maintainer of zigler (https://zigler.hexdocs.pm/Zig.html), and I have my own pharma startup. I currently use zig in two contexts:
1) wraps a proprietary .so file that is used to communicate with a scientific (microscope) digital camera, in a nice BEAM-module-shaped interface. Sorry, code is private.
2) I have a vue.js component that does DNA editing, and one of the features is DNA sequence alignment, and so I had claude write the smith-waterman lalign algorithm in zig, and it compiles to wasm, and this plus going from O(N^2) to O(N) dropped the runtime of an alignment from 30s to a few hundred ms, in both cases so much better than ~5m using a web SAAS, good enough that I can render alignments on-demand, and I don't have to do a storage layer for alignments.
https://github.com/Vidala-Labs/opengenepool/tree/master/src/...
you can play with it at:
https://opengenepool.vidalalabs.com
Two sequences may be aligned by right-clicking a sequence name when a sequence is loaded, it will align the two sequences (you'll have to create a second sequence as it forbids aligning a sequence with itself).
Why zig? Because it just makes things like cross-compilation easier (microscope is mounted on a elixir nerves deployment!), and has less footguns, and doesn't hide away things you might care about. Most of the things i build with zig don't really have a concern about memory safety, or have such trivial memory patterns that it's easy to verify by eye that they're memory safe.
> microscope is mounted on a elixir nerves deployment!
What do you mean? I imagine that there is an Elixir application running on an embedded system and maybe it relies on an external binary application (compiled using zig). If so, how do you manage the communication between them?
Incidentally there is a second proprietary industrial camera on there that I have running as a different NIF built in zig, but truth be told I probably could have had claude do that one in pure elixir. However, claude did write a harness that I used to inspect the usbpcap dumps (and create the camera interface), that was done in zig for simplicity and turning it into a nif using zigler was a trivial transformation for claude to do.
It is a really interesting interview. However, Zig code is a bit hard for me to read.
I liked the way Andy talked throughout the video, and also thought the interviewer's questions were very good.
Of course there’s lots of programming that can afford to pay for GC side effects, if there weren’t we wouldn’t have invented GC, but it’s a little less universal, a little less ‘system’.
For me, I came to Zig after horrible cross-platform experiences led me to try going all the way back to C and I found that I was spending way too much time learning to deal with accidental complexity instead of essential complexity. (Respect to the C masters but I failed to adapt.)
But people don't write those in Go, they use Rust for it
To your point though, if Rust is getting it done, go for it. A lot of people still write those in C though.
Which is why TamaGo, TinyGo and gVisor exist.
https://www.erlang.org/docs/25/man/erts_alloc.html
jvm has at least 5:
https://github.com/openjdk/jdk/blob/master/src/hotspot/share...
postgres has at least 8:
https://github.com/postgres/postgres/blob/master/src/backend...
since zig anoints an allocator interface in its stdlib, your (and the stdlib's) data structures which use allocators can be trivially reused across different allocation strategies without rewriting code; and very likely (not guaranteed ofc) if you bring in someone else's code they will cleave to convention.
const Sampler = @SpirvType(.sampler);
^
const Image = @SpirvType(.{ .image = .{
.usage = .{ .sampled = u32 },
.format = .unknown,
^
} });
Everything else about zig is quite readable, but this gets me every time. Maybe I'm being dumb though.So ".unknown" is a standin for "SomeEnum.unknown" or "SomeStruct.unknown", depending on what .format is.
I thought it was used in four completely separate ways:
· normal struct field access
· anonymous struct definition
· field definition within structs (for reasons to do with the parser)
· an extra 'infer operator' for syntactic sugar
But there's no support for anonymous structs/fields, and all structs and fields require a type somewhere for it to be inferred. Which is why this is invalid zig:
const test = .{ .x = 0, .y = 1 };
(It would need the type to be specified in the called function definition, or inline when assigning)Correct me if I'm wrong here! (And thank you)
const Enum = enum {one, two, five};
const t: Enum = .one; // Enum.one, but the type was inferred from lhs
std.debug.print("{t}\n", .{t});
Defining an anonymous struct is valid in zig; your example is only invalid because "test" is a reserved keyword. But you are correct that it reifies into a concrete type, and after initialization it doesn't coerce into other types because zig doesn't do structural typing: const anonymous = .{ .x = 0, .y = 1 };
std.debug.print("{}\n", .{@TypeOf(anonymous)}); // will output something like test_0__struct_45138
const Point = struct { x: i32, y: i32 };
const p1 = Point{ .x = 0, .y = 1 }; // valid, explicit struct literal
const p2: Point = .{ .x = 0, .y = 1 }; // valid, anonymous struct will coerce to Point
//const pt: Point = anonymous; // error: expected type 'test_0.Point', found 'test_0__struct_45138'
And then there's fieldless anonymous structs aka tuples. I'm including them because they were used in the print statements above: const tuple = .{ 0, "1", true };
std.debug.print("{}\n", .{@TypeOf(tuple)});
// struct { comptime comptime_int = 0, comptime *const [1:0]u8 = "1", comptime bool = true }All this does for me is raise the question of why they chose to use the `.` for so many different uses. I'd be fine if it was just to infer the type, but it seems very overloaded.
Note that even C creators proposed changes to WG 14 (not accepted), and later on moved on with their own approaches with Alef, Limbo and finally Go.
If you're comfortable with C you will stick with C and benefit from it being the lingua franca, and not struggle to find support or talent.
This is true both at work or OS.
As a very talented C dev told me, I can see the point or go or Rust that would solve some of my issues, Zig while being an improvement doesn't solve any of my real pain points.
I've written a decent amount of Zig, Go, and C, and I often describe Zig as in between Go and C. So your untrained eye seems pretty accurate. Maybe it's just not the tool you need, and that's fine! I could get excited about a new tractor that is well built and leans heavily into right to repair activism, but I live in a city and wouldn't use it.
I think one of the selling points is that the language does not come to your way and allows unsafe constructs. So, I think the target audience is roughly at C and C++ users.
Zig looks like a good middle ground.