I am doing the Exercism exercises for Zig and decided to document my progress since, with every exercise, I learn new and interesting information.
The first exercise is doing a “Hello World” and the end solution is simple:
pub fn hello() []const u8 {
return "Hello, World!";
}
The interesting part is that Zig represents string literals as []const u8
, which is an array of bytes.
const foo = "Hello"
is (almost) the same as const foo = [_]u8{ 'H', 'e', 'l', 'l', 'o' };
Let’s break it down:
const
means it’s a constant value or immutable.
[_]u8
means it’s an array of bytes.
- The compiler will infer the
[_]
 to [5]
.
Where the string uses double quotes (“H”), and the individual characters (‘H’) use single quotes.
But there is more because a string also contains a sentinel value. A sentinel value is something that indicates the end of a sequence. A popular choice of a sentinel value is the value 0
, also called the null character.
Zig supports sentinel-terminated arrays, slices, and pointers:
//. This is taken from Ziglings, exercise 76.
// const a: [4:0]u32 = [4:0]u32{1, 2, 3, 4};
// const b: [:0]const u32 = &[4:0]u32{1, 2, 3, 4};
Array a
stores five values, the last of which is a 0
. Array b
is only allowed to point to zero-terminated arrays.
Now we can determine the actual type of a string in Zig:
@TypeOf("foo") == *const [3:0]u8
Translated to English, a string is a “constant pointer to a null-terminated fixed-size array of u8”.
Now, why would you still have a 0
at the end when you know the size of the array? That’s because Zig strings are compatible with C strings, which are also null-terminated.
If you want to learn more about strings, I can recommend the Zig / Strings in 5 minutes article.
With Zig, I usually run zig build test
to run all the tests. However, sometimes, I need to run a single test, and that option is not available through the build
command.
In this case, I learned you can use the zig test
command along with the --test-filter
option.
For example, to test a single test named “lexer initialization” located in src/lexer/lexer.zig
, you can run zig test --test-filter “lexer initialization” src/lexer/lexer.zig
. It’s worth noting that the test filter doesn’t have to be the full name of the test to be able to find it.
I used to manage my Zig installation by installing the binary and copying to my path. Not too hard, but now there is even an easier way by using zigup
.
To install zig, it enables you to simply type zigup master
I would recommend to get the latest binary from the Github releases page. Or if
you already have Zig, install it from source:
# Install the binary
git clone [email protected]:marler8997/zigup.git
cd zigup
zig build -Dfetch
cp zig-out/bin/zigup ~/.local/bin/
I have been tinkering with Zig lately and to get a grasp on the language
I have been using the excellent Ziglings quizes.
In Quiz #58 there is a great comment which lists the different types for Zig:
//
// We've absorbed a lot of information about the variations of types
// we can use in Zig. Roughly, in order we have:
//
// u8 single item
// *u8 single-item pointer
// []u8 slice (size known at runtime)
// [5]u8 array of 5 u8s
// [*]u8 many-item pointer (zero or more)
// enum {a, b} set of unique values a and b
// error {e, f} set of unique error values e and f
// struct {y: u8, z: i32} group of values y and z
// union(enum) {a: u8, b: i32} single value either u8 or i32
//
// Values of any of the above types can be assigned as "var" or "const"
// to allow or disallow changes (mutability) via the assigned name:
//
// const a: u8 = 5; // immutable
// var b: u8 = 5; // mutable
//
// We can also make error unions or optional types from any of
// the above:
//
// var a: E!u8 = 5; // can be u8 or error from set E
// var b: ?u8 = 5; // can be u8 or null