I've been learning zig for my game development project, read more about it here. Those are my initial (mostly positive) impressions of the language, coming from a mostly JS/TS recent experience.
Error handling
Errors are values - It's quite a popular opinion at this point that exceptions aren't the best. They create a hidden control flow, and in JavaScript they can't even be declared; which makes your applications much more unstable.
Zig uses error enums and nice syntactical sugar for easy and fun error handling. For example :
fn failingFunction() error{MyError}!void {
return error.MyError;
}
pub fn main() !void {
try failingFunction();
}
In the code above we declare an error MyError
(This can also be done separately) and return it.
The try
means "if this returns an error, return it here" as in:
failingFunction() catch |err| return err;
I believe this approach is a great combination and saves us from the endless if (err != nil)
in Go land.
Other highlights:
- Errors are explicit, all types have to be declared and handled
- Handling is done right then and there, not on the block level
- Thanks to payload capturing, errors are typed correctly and autocompeleted, making it easy to use something like a switch expression.
The !void
syntax - !
is used to create a union between the return type and the error types. Zig supports not adding any errors prior to the !
, which is supposed to create a union of all the errors that you actually return from the function.
In practice, I find this syntax unhelpful. At least with my IDE experience I don't get any intellisense in this case, and it makes the function less clear. Just tell me what you are gonna return!
I only see it being useful on the main()
function.
Payload capturing
You know how in TS you might have a type like number | undefined
? You might use an if
or some logic to narrow down the type to what you need, and TS automatically displays the new type correctly.
While it's easy, there are problems with this approach:
- If types can change throughout a function, it's harder to follow
- In some cases you still have to do a cast
In Zig, you do this with "Payload Capturing". You can "capture" aka create a new immutable variable with the resulting type. For example:
const maybe_num: ?usize = 10; // `?` Means it can be `null`
if (maybe_num) |num| {
// Use num
}
It's very clear what's happening! Moreover, the variable is immutable, but if you really need to change it, you can capture a pointer to the value instead.
It's also worth mentioning that this mechanism can be used throughout the language, including: for
, switch
, catch
, etc.
Comptime shenanigans
Admittedly I didn't yet grasp the full possibilities of comptime. But in short, you can run regular code during compilation. You may create whole functions that are only used during this time, and can return compilation errors if necessary.
It suits Zig quite well, because it's a very malleable language. Even types are values, meaning you can create, change, and get information about types (Especially in comptime).
A basic example of this from the Zig Guide:
const a = 5; // When a number type isn't specified, it defaults to comptime_int
const b: if (a < 10) f32 else i32 = 5;
// b: f32 after compilation
Editor experience
I'm using VSCode with the official Zig plugin (which uses zls
). The intellisense and errors I see in the editor leave much to be desired.
"detectable illegal behavior" aka illegal things that will result in a compilation error aren't typically displayed in the editor. For example:
const nums = [3]u8{ 2, 1, 3 };
_ = nums[4]; // Index out of bounds error
I'm on the 0.14 (dev) master branch version, if it's supposed to work let me know in the comments!
EDIT:
I was able to enable this type of error checking in VSCode by enabling the "Enable Build On Save" option under the zig extension settings.
Which is exactly what it sounds like, saving the file will rebuild the app and show any errors resulting from compilation on the correct lines. 🤦
Top comments (1)
P.S. I could go on an on about benefits of Zig over TS when it comes to standard features of statically typed (and good) languages. But I guess this point is long established...