I’ve written some practice projects when I learned C before, but I haven’t tested them. there are more unit testing frameworks in C, so I don’t know which one to choose, so I might as well just use Zig to do the testing. I just saw this article Testing and building C projects with Zig, and I feel it is a good choice. I’ve heard about Zig for half a year, and I’m interested in the libc-independent, better C interop, and robust features.
The code involved in this article can be found at this GitHub repository.
Hello World
First, take a look at the examples on the Zig official language site to get a first-hand feel for what the language looks like.
|
|
The above code is partially commented as a brief introduction to Zig syntax, and the following section focuses on the syntax of the json parsing part.
-
Signature of the function
json.parse
.1
parse(comptime T: type, tokens: *TokenStream, options: ParseOptions) ParseError(T)!T
- The comptime keyword in the first argument indicates that the argument is executed at compile time, and the type of T is type, indicating a type value.
- T indicates that the return value may be wrong, similar to the Result type in Rust.
- ParseError(T) is executed at compile time to derive the specific error type. zig uses comptime to implement the generic type.
-
json.parse(Config, &stream, . {})
in. {}
denotes an anonymous struct whose type can be derived by the compiler. The fields are not initialized within the struct because the characters in ParseOptions have default values. -
break :x
means exit block fast, followed by return value. -
res catch unreachable
means take out the value of the normal case, equivalent to res.unwrap() in Rust.
The sample code above shows some of the syntax specific to Zig, most notably comptime, which is the basis for Zig’s implementation of generic types, where types are first-class members that can make function calls or assignments. Take for example the following example.
Here the LinkedList defines a generic linked list, the type of which is determined at compile time. It is used in the following way.
|
|
Pointers
The above section took the reader through the basic syntax of Zig. This section introduces another clever design in Zig: pointers.
A pointer is the closest machine abstraction in C. It represents a memory address, which is fine if it is a basic type like int32, where the compiler can determine the length of the pointing, but if it is an array, a pointer alone cannot determine the number of elements and usually requires an additional length field to be saved.
Zig improves on this by defining the following three pointer types.
*T
A pointer to an element, e.g.*u8
[*]T
pointers to multiple elements, similar to pointers to arrays in C, with no length information*[N]T
pointer to an array of N elements, length information can be obtained fromarray_ptr.len
In addition to the three pointers mentioned above, one of the most common structures used by Zig is slice, of the form []T
. It can be seen as the following structure.
The difference between slice []T
and array [N]T
is that the length of the former is determined at runtime, while the latter is determined at compile time.
In addition, to facilitate interaction with C, Sentinel-Terminated Pointers is defined in Zig with the syntax [*:x] T
, which means that it ends with x
. Zig string literals are of type *const [N:0]u8
.
[*:0]u8
and [*:0]const u8
can be equated to string in C.
These types of pointers can be converted to each other, and this is where beginners tend to get confused when they first start writing code, and the following examples give the common conversions.
|
|
C interop
The Zig command line tool integrates with clang, so zig can be used to compile not only Zig code, but also C/C++ code, and zig cc
is very popular in the community, as in the following article.
Zig Makes Rust Cross-compilation Just Work · Um, actually…
Thanks to the libc-independent nature of zig, zig can be used to cross-compile very easily.
In addition to command-line tools, zig also provides good support for C at the language level, allowing direct reference to C code.
Zig also integrates pkg-config to facilitate linking to the rich library in the C community, which can be imported directly in the code with @cImport
.
|
|
In addition, Zig provides the zig translate-c
command to convert C code to Zig code, which can be referred to when you encounter difficulties in calling C libraries. For pointers in C, Zig uses [*c]T
, which is called C pointer, and supports the same operation as pointers in Zig, which generally translates C pointer to Optional Pointer ? *T
to use, null
means that the C pointer is null.
In order for C to be able to call Zig, one thing needs to be noted.
struct/enum/union has no memory structure guarantees by default and needs to be declared to conform to the C ABI memory format using the extern
or packet
keywords.
The main reason for not having memory structure guarantees by default is to facilitate Zig to perform optimizations such as
- field reordering to achieve smaller size with guaranteed alignment
- Aligning fields in the right way to guarantee the fastest access speed
- More can be found at the following link.
In Rust structs are also not guaranteed memory structures by default, for reasons similar to zig. Here’s a small example from C.
|
|
Here is an example of exporting Zig as a lib for C to call (the current version of Zig does not export header files, you need to write them by hand, see #6753).
Finally, compile it with the following build script.
|
|
Memory Control
Rust uses strict ownership support to ensure memory safety, how does Zig solve this problem? The answer is: Allocator, first look at an example.
page_allocator
is one of the memory allocators provided by Zig. You can see that the usage is similar to that in C. After using the allocated memory, you need to free
manually, so how does Zig ensure memory safety? The answer is GeneralPurposeAllocator
, this allocator will prevent double-free, use-after-free, leaks, etc., for example.
|
|
Zig is a balance between C and Rust by providing a variety of allocators to meet the needs of different scenarios, which is neither as dangerous as C nor as strict as Rust. For more information on how to choose an allocator, see: Chapter 2 - Standard Patterns | ziglearn.org
Peripheral tools
Build
The command line tools in zig are sufficient, no need for additional tools like make
zig build
build, description file isbuild.zig
zig init-exe
initializes the application projectzig init-lib
initializes the class library project
But there is no package manager yet, there are currently two in the community, gyro and zigmod. More can be found at https://ziglearn.org/chapter-3/#packages
Development
Currently there is zls, an officially supported language server, supported by all major editors, and zig fmt
for code formatting.
Documentation
There is a serious lack of documentation, and you need to refer to the library source code to write code. Fortunately std is of high quality, relatively easy to read, and zls can jump automatically.
Zen
|
|
Summary
This article is a preliminary introduction to the Zig language, not designed to multi-threading, async and other content, which is relatively advanced, so we will introduce it later when we have more experience.
As a modern language, many features of Zig are designed from the disadvantages of C, so it avoids many problems, which is very valuable. According to the current latest roadmap, 1.0 will be released in 2025 at the earliest, which may be a long process, but that doesn’t stop enthusiasts from trying! First step, start by joining the community!