I have started to experiment with Zig.
On Windows 10, I downloaded the zip file, unpacked it at "C:\\zig" and added "C:\\zig" to PATH. Opening Powershell:
> zig version 0.10.0-dev.2033+3679d737f > zig zen * Communicate intent precisely. * Edge cases matter. * Favor reading code over writing code. * Only one obvious way to do things. * Runtime crashes are better than bugs. * Compile errors are better than runtime crashes. * Incremental improvements. * Avoid local maximums. * Reduce the amount one must remember. * Focus on code rather than style. * Resource allocation may fail; resource deallocation must succeed. * Memory is a resource. * Together we serve the users.
From the documentation, we have the "Hello World" program:
const std = @import("std"); // <1> pub fn main() !void { // <2> const stdout = std.io.getStdOut().writer(); // <3> try stdout.print("Hello World", .{}); // <4> }
-
The "std" library is imported, and named
std
. - The return type has an "!" as the function can give an error.
-
Accesses and names
stdout
through thestd
library. -
Uses
print
onstdout
. As this can fail, we use atry
expression which will make any error abort the function.
Saved into a file "hello.zig", this can be run directly:
> zig run hello.zig Hello World
One of my favourite features is that we can compile programs into an executable:
> zig build-exe hello.zig > dir Directory: C:\Users\peter\projects\zig Mode LastWriteTime Length Name ---- ------------- ------ ---- d----- 04/05/2022 16:47 zig-cache -a---- 04/05/2022 16:47 417280 hello.exe -a---- 04/05/2022 16:47 1011712 hello.pdb -a---- 04/05/2022 16:46 149 hello.zig
The executable size or speed can be controlled using different build modes. These are "Debug", the default, and then three "Release" modes - fast, safe or small. Using the small mode reduces the executable size to kilo-bytes:
> zig build-exe hello.zig -O ReleaseSmall > dir Directory: C:\Users\peter\projects\zig Mode LastWriteTime Length Name ---- ------------- ------ ---- d----- 04/05/2022 16:47 zig-cache -a---- 04/05/2022 16:49 7168 hello.exe -a---- 04/05/2022 16:49 86016 hello.pdb -a---- 04/05/2022 16:46 149 hello.zig
This is the classic "guess the number" game, where the computer picks a number:
const std = @import("std"); // Displays greeting to stdout fn printGreeting() !void { const stdout = std.io.getStdOut().writer(); // <1> try stdout.print("Guess the Number (from 1 to 100)\n", .{}); } // Returns a random number from 1 to 100, inclusive fn chooseTarget() u32 { const seed = @intCast(u64, std.time.milliTimestamp()); // <2> var rnd = std.rand.DefaultPrng.init(seed); // <3> return 1 + rnd.random().uintLessThan(u32, 100); // <4> } // Return a number from stdin or an error if no number found fn readNumber() !u32 { var number: u32 = 0; const stdin = std.io.getStdIn().reader(); var buf: [10]u8 = undefined; if (try stdin.readUntilDelimiterOrEof(buf[0..], '\n')) |input_line| { // <5> var line = input_line; // for windows, check if penultimate character is \r // if so, remove it if (line[line.len - 1] == '\r') { // <6> line = line[0..(line.len - 1)]; } number = try std.fmt.parseInt(u32, line, 10); // <7> } return number; } // Main function, runs the game pub fn main() !void { const stdout = std.io.getStdOut().writer(); const target = chooseTarget(); var guess: u32 = 0; try printGreeting(); while (target != guess) { try stdout.print("What is your guess? ", .{}); guess = readNumber() catch { // <8> // not a number, so exit try stdout.print("Exiting game\n", .{}); break; }; if (target == guess) { try stdout.print("Correct!\n", .{}); break; } else if (target < guess) { try stdout.print("Your guess is too high\n", .{}); } else { try stdout.print("Your guess is too low\n", .{}); } } try stdout.print("I chose {d}\n", .{target}); }
-
Separate function to print greetings - still returns an error, and requires
stdout
writer to be extracted. -
Getting a
seed
relies on the useful@intCast
to safely cast between incompatible int types. (NB: on version 0.11.0, the syntax for@intCast
has changed, so rewrite this line as@as(u64, @intCast(std.time.milliTimestamp()))
.) - Creates a random number generator with given seed.
-
uintLessThan
returns a number less than 100, from 0 to 99. -
Reads from
stdin
intobuf
until the given delimiter is reached - platform specific. - Tidy up the extra delimiter in Windows.
-
The
line
can then be parsed, returning an error if it's not a number. -
Catch error from
readNumber
, as any non-numbers are a reason to exit.
Running and interacting looks like:
> .\guess-number.exe Guess the Number (from 1 to 100) What is your guess? 50 Your guess is too low What is your guess? 75 Your guess is too high What is your guess? 63 Your guess is too high What is your guess? 56 Your guess is too low What is your guess? 60 Your guess is too low What is your guess? 61 Correct! I chose 61 > .\guess-number.exe Guess the Number (from 1 to 100) What is your guess? 50 Your guess is too high What is your guess? quit Exiting game I chose 28