Zig is a systems programming language that emphasizes simplicity, performance, and safety. It has gained attention for its minimalistic approach and powerful features. Zig is particularly well-suited for developing low-level systems and applications where control over hardware and performance are critical.

Features of Zig

  1. No hidden control flow: Zig has no hidden control flow, which means that the behavior of the program is predictable and easy to understand. This makes debugging and optimization easier.
  2. Error handling: Zig has a unique error handling mechanism that is designed to be simple, efficient, and safe. It uses compile-time checks to ensure that errors are handled correctly.
  3. Compile-time execution: Zig supports compile-time execution, which allows developers to run code at compile time to generate data or perform computations. This feature can be used to generate code, perform optimizations, or validate data.
  4. Memory Management: Zig provides fine-grained control over memory management, allowing developers to manage memory allocation and deallocation manually. This can help reduce memory overhead and improve performance.
  5. Interop with C: Zig has excellent interoperability with C, allowing developers to call C functions and use C libraries directly. This makes it easy to integrate Zig code with existing C codebases.

Variables

In Zig, variables are declared using the var keyword followed by the variable name and type. Zig supports type inference, allowing developers to omit the type when it can be inferred from the value.

Syntax and Example:

var a: i32 = 10; // Explicit type declaration

const Pi: f64 = 3.14; // Constants
const World: []const u8 = "Hello, World";

var x: i32 = 42; // Type inferred as i32
var y = "Hello, Zig"; // Type inferred as []const u8

Control Flow

Zig provides a range of control flow statements, including if, else, switch, and looping constructs like while and for.

Syntax and Example:

// If statement
if (x > 0) {
    // Code block
}

// For loop
for (std.range(0, 10)) |i| {
    std.debug.print("i is {}\n", .{i});
}

// While loop
var i: i32 = 0;
while (i < 10) {
    // Code block
    i += 1;
}

// Switch statement
const fruit = "apple";
switch (fruit) {
    "apple" => // Code block
    "banana" => // Code block
    else => // Default case
}

Functions

Functions in Zig are declared using the fn keyword followed by the function name and parameters.

Syntax and Example:

// Function Declaration
fn add(a: i32, b: i32) i32 {
    return a + b;
}

// Function Call
const result = add(10, 20);

// Function returning multiple values
fn calculate(a: i32, b: i32) (i32, i32) {
    const sum = a + b;
    const product = a * b;
    return (sum, product);
}

// Function Call with multiple return values
const result = calculate(10, 20);
const sum = result[0];
const product = result[1];

// Anonymous functions
const add = fn(a: i32, b: i32) i32 {
    return a + b;
};

Data Structures ( Structs )

Zig supports user-defined data structures using the struct keyword. Structs can contain fields of different types and can be used to represent complex data.

Syntax and Example:

// Struct Declaration
const Person = struct {
    name: []const u8,
    age: i32,
};

// Struct Initialization
var p = Person{ .name = "Alice", .age = 30 };
// Accessing fields
const name = p.name;

// Methods
const Circle = struct {
    x: f64,
    y: f64,
    r: f64,

    // area method for Circle
    fn area(self: *const Circle) f64 {
        return 3.14 * self.r * self.r;
    }
};
// Method Call
var c = Circle{ .x = 0.0, .y = 0.0, .r = 5.0 };
const area = c.area();

Collection Manipulation

Zig provides various types for managing collections, such as arrays, slices, and hash maps. Here’s how you can manipulate these collections in Zig.

Syntax and Example:

// Arrays
var arr = [_]i32{1, 2, 3, 4, 5};
const firstElement = arr[0];

// Slices
var allocator = std.heap.page_allocator;
var nums = try std.ArrayList(i32).init(allocator);
nums.append(10);
nums.append(20);
const firstElement = nums.items[0];
// Iterating over a slice
for (nums.items) |num| {
    std.debug.print("Number: {}\n", .{num});
}
// Deallocating the slice
nums.deinit();

// Hash Maps
var allocator = std.heap.page_allocator;
var map = try std.HashMap(i32, i32).init(allocator);
map.put(1, 10);
map.put(2, 20);
if (map.get(1)) |value| {
    std.debug.print("Value for key 1: {}\n", .{value});
} else {
    std.debug.print("Key 1 not found\n", .{});
}
// Iterating over a hash map
for (map.items()) |key, value| {
    std.debug.print("Key: {}, Value: {}\n", .{key, value});
}
// Deallocating the hash map
map.deinit();

Error Handling

Zig handles errors using a built-in error type and the try keyword. This approach promotes explicit error handling and ensures that errors are checked and handled properly.

Syntax and Example:

// Function that returns an error
fn sqrt(x: f64) !f64 {
    if (x < 0) {
        return error.NegativeNumber;
    }
    return std.math.sqrt(x);
}

// Error handling when calling the function
const result = sqrt(-16.0);
switch (result) {
    error.NegativeNumber => {
        std.debug.print("Error: square root of negative number\n", .{});
    },
    |value| {
        std.debug.print("Square root: {}\n", .{value});
    },
}

Ecosystem

Zig has a growing ecosystem of libraries, tools, and frameworks that make it easier to develop applications in Zig.

Installation

brew install zig

Hello World in Zig

create zig project

zig init

file: src/main.zig

const std = @import("std");

pub fn main() void {
    const message = "Hello, Zig!";
    std.debug.print("{}", .{message});
}

build and run

zig build run