Zig is a systems programming language designed to be robust, fast, and easy to learn.
It offers manual memory management, low-level control, and is an excellent choice for those looking to develop high-performance systems and software.
Installation
Install Zig on Linux
sudo apt install zig
Install Zig on Windows
- Download the latest version of Zig from the official site.
- Extract the ZIP file and add the directory to the
PATHenvironment variable.
Verify the installation
To verify that Zig is installed correctly, run:
zig version
Introduction
Basic Compilation
To compile a Zig file
zig build-exe file.zig
Direct Execution
To run directly
zig run file.zig
Basic Structure of a Zig Program
const std = @import("std");
pub fn main() void {
const stdout = std.io.getStdOut().writer();
stdout.print("Hello, Zig!\n", .{}) catch {};
}
Data Types in Zig
Zig has simple data types, such as integers, floating-point numbers, and pointers, as well as composite types like structures and arrays.
Integers
i8,i16,i32,i64: Signed integers.u8,u16,u32,u64: Unsigned integers.
var a: i32 = 42;
var b: u64 = 100;
Floating-Point Numbers
f16,f32,f64: Floating-point numbers.
var pi: f32 = 3.14159;
Booleans
bool: Can betrueorfalse.
var is_open: bool = true;
Strings
- Strings are arrays of characters (
[]const u8).
const greeting: []const u8 = "Hello, Zig!";
Variables and Constants
Variables
Declared with var. Their value can change.
const pi: f32 = 3.14; // constant
var counter: i32 = 0; // variable
Constants
Declared with const. Their value cannot change.
const pi: f32 = 3.14; // constant
var counter: i32 = 0; // variable
Operators
Arithmetic
- Addition:
+ - Subtraction:
- - Multiplication:
* - Division:
/ - Modulo:
%
var x = 10 + 5; // 15
var y = 10 % 3; // 1
Comparison
- Equal:
== - Not equal:
!= - Greater:
> - Less:
< - Greater or equal:
>= - Less or equal:
<=
if x == 10 {
// code
}
Control Flow
Conditionals
If - Else
if x > 10 {
// code if the condition is true
} else {
// code if the condition is false
}
Switch
switch (x) {
1 => std.debug.print("One\n", .{}),
2 => std.debug.print("Two\n", .{}),
else => std.debug.print("Another number\n", .{}),
}
Loops
For
for (i32(i = 0); i < 10; i += 1) {
std.debug.print("i: {}\n", .{i});
}
While
var i: i32 = 0;
while (i < 10) : (i += 1) {
// code that runs while i is less than 10
}
For in collections
const arr = [_]i32{1, 2, 3};
for (arr) |value| {
// value contains the elements of the array
}
Functions
Function Definition
Zig allows defining functions with arguments and return values.
fn sum(a: i32, b: i32) i32 {
return a + b;
}
- The
pubprefix makes the function public (visible from other Zig files). - If nothing is returned, the return type is
void.
Function Call
const result = sum(5, 3);
std.debug.print("Result: {}\n", .{result});
Anonymous Functions (lambdas)
const multiply = fn (a: i32, b: i32) i32 {
return a * b;
};
Inline Functions
fn square(x: i32) i32 {
return x * x;
}
const result = square(5);
Generic Functions
fn swapGeneric(behavior: anytype, a: anytype, b: anytype) void {
var temp: anytype = a;
a = b;
b = temp;
}
Composite Types
Arrays
Arrays are fixed-length types.
const numbers: [5]i32 = [5]i32{1, 2, 3, 4, 5};
Slices
const slice = &numbers[1..4]; // Access elements from index 1 to 3
std.debug.print("Elements in slice: {}\n", .{slice});
Structures
Structures (structs) are custom data types that can contain multiple values.
const Point = struct {
x: i32,
y: i32,
};
const point: Point = Point { .x = 10, .y = 20 };
Enumerations
Enumerations (enum) define a set of possible values for a variable.
const State = enum {
Inactive,
Active,
};
const state: State = State.Active;
Unions
const Value = union {
integer: i32,
decimal: f32,
};
var v: Value = Value{ .integer = 10 };
std.debug.print("Integer value: {}\n", .{v.integer});
Memory Management
Pointers
Zig allows manual pointer management.
var x: i32 = 42;
var ptr: *i32 = &x;
Memory Allocation
const memory = std.heap.page_allocator;
const ptr = try memory.allocate(i32, 1);
ptr.* = 42;
defer memory.deallocate(ptr);
Memory Release
Zig does not have a garbage collector, so memory allocation and release must be done manually.
var allocator = std.heap.page_allocator;
const buffer = try allocator.alloc(u8, 1024); // allocate 1024 bytes
defer allocator.free(buffer); // release memory
Defer
defer executes a statement just before the function ends. Useful for releasing resources.
const std = @import("std");
pub fn main() void {
var file = try std.fs.cwd().openFile("file.txt", .{});
defer file.close(); // executed just before the function ends
}
Error Handling
Zig does not use exceptions. Instead, it employs an error handling system based on the use of try, catch, and error codes.
Explicit Errors
Functions can return errors that must be handled.
fn division(a: i32, b: i32) !i32 {
if (b == 0) {
return error.DivisionByZero;
}
return a / b;
}
Try - Catch
try: Executes an operation that may fail. If it fails, it returns the error to the caller.catch: Catches errors.
const file = try std.fs.cwd().openFile("file.txt", .{}).catch |err| {
std.debug.print("Failed to open file: {}\n", .{err});
};
Asserts
const a: i32 = 5;
const b: i32 = 10;
assert(a < b, "a must be less than b");
Concurrency
Zig has support for asynchronous tasks using async and await.
Defining Asynchronous Functions
fn get_data_async() async i32 {
return 42;
}
pub fn main() void {
const data = await get_data_async();
}
Modules
Creating Modules
Create a file named my_module.zig.
pub fn moduleFunction() void {
std.debug.print("Module function called\n", .{});
}
Importing Modules
const std = @import("std");
const my_module = @import("my_module.zig");
pub fn main() void {
my_module.moduleFunction();
}
Interoperability with C
Zig allows importing and using C code directly.
Importing C
const c = @cImport({
@cInclude("stdio.h");
});
pub fn main() void {
c.printf("Hello from C!\n");
}
Zig can also compile C files along with Zig. This is useful for integrating C libraries into Zig projects.