6 ms·
Zig's "fancy" (I don't think they're that fancy) type features that IMO make it a great C alternative are: - non-null pointers, and distinct types for single-i
by dundarious 5y ago
Zig's "fancy" (I don't think they're that fancy) type features that IMO make it a great C alternative are:
- non-null pointers, and distinct types for single-item pointers and multi-item pointers (multi-item pointers are rarely used except indirectly via slices, so unchecked pointer arithmetic errors are largely banished)
- builtin tagged unions (AKA algebraic data types) with very pleasant to use switch logic -- it can't be overstated how nice the "handle all the cases" logic is in Zig in general (catch, orelse, if-else/switch expressions)
- a decent proposition for errors (the error union, and try keyword), but I haven't decided if I really like it yet
There's lots of non-type stuff there too. I was writing personal projects in C without libc, but found there to be a lot of annoying work involved -- happy to do it, but it's not earned/fruitful annoyance, more like a long list of incidental historical annoyances. Zig seems to cater to the same level of the stack, but with all that boring stuff taken care of.
- spc476 5y agoSo it sounds like Zig makes a distinction between pointers and arrays. Am I reading that right?
- dundarious 5y agoYes, arrays are another distinction: - an array [3]u8 - a single item non-nullable pointer *u8 - a single item nullable pointer ?*u8 - a multi-item non-nullable pointer [*]u8 - a multi-item nullable pointer ?[*]u8 - a slice []u8 Typically your API is just made up of slices and non-nullable single item pointers. Arrays are just the typical backing store for a slice, that you might define in main or for small scratch buffers. Here's a typical example of an OS read (where the fd has been set to non-blocking already): fn readUpTo32Bytes(fd: std.os.fd_t) ![]u8 { var array: [32]u8 = undefined; var data = array[0..]; // slice of entire array data.len = std.os.read(fd, data) catch |err| switch (err) { error.WouldBlock => 0, else => return err, }; handle(data); } fn handle(data: []const u8) void { ... } If you don't need to handle EAGAIN/error.WouldBlock, then just do `data.len = try std.os.read(fd, data);` I didn't explain the `!` in the `![]u8` return type, but it's basically saying "an error or a []u8", where it's compile time known what the full set of errors is (in this case every error std.os.read can return, minus WouldBlock).
- dundarious 5y agoReplying to note I mistakenly left in the `![]u8` return type when it's not returning any byte slice. Should be `!void`.
- _vvhw 5y agoAlso Zig's memory alignment in the type system is great for doing low-level I/O. Coming from C, I find that Zig's memory alignment options are easier and more powerful.
- dundarious 5y agoTotally agree. Having std.os.mmap enforce correct page_size alignment for the base pointer has saved me once already (using mmap and memfd for a fixed size circular buffer where you can always provide contiguous bytes for the full size available, without any memmove).
- jibalt 5y agoMany of Zig's fancy type features are in the library, not in the language, because types can be programmatically constructed at compile time.