Basics

Operators
Keywords
Access Modifiers
  • Chat-gpt: " Default:  There is no native visibility control."

    • pub  is related to code organization and modularity, but is not tied to dynamic runtime security rules .

    • Zig uses modular organization to control access between different parts of the code.

    • Public ( pub ) : Used to expose functions, types or variables from a module to other modules.

    • Private (default) : Items defined without pub  are private to the current scope (file or block)."

Comments, Print, Formatting

Comments
//! Top-level documentation.

/// Documentation comment.

// Simple comment.
Suppress Warnings
//suppress unused constant compile error    
_ = .{ a, b, c, d };
Print
  • No automatic \n .

  • "Prints to stderr (it's a shortcut based on std.io.getStdErr() )".

  • Part of the standard library ( std ).

  • Uses std.debug.print() .

  • Writes directly to standard output ( stdout ).

  • Accepts string formatting, similar to printf  in C.

const std = @import("std");
const print = std.debug.print

pub fn main() void {
    print("Value: {}\n", .{42});
}
Info
  • With automatic \n .

  • Part of the std.log  module.

  • Uses std.log.info() .

  • Unlike print , info  can be filtered by log levels (like debug , warn , err ).

  • Output can be redirected or configured depending on the compiler and runtime environment.

const std = @import("std");

pub fn main() void {
    std.log.info("Value: {}", .{42});
}
Formatting
  • std.fmt  provides ways to format data to and from strings.

  • A basic example of creating a formatted string. The format string must be compile-time known. The d  here denotes that we want a decimal number.

const std = @import("std");
const expect = std.testing.expect;
const eql = std.mem.eql;
const test_allocator = std.testing.allocator;

test "fmt" {
    const string = try std.fmt.allocPrint(
        test_allocator,
        "{d} + {d} = {d}",
        .{ 9, 10, 19 },
    );
    defer test_allocator.free(string);

    try expect(eql(u8, string, "9 + 10 = 19"));
}
  • std.debug.print :

    • "it writes to stderr and is protected by a mutex."

    const std = @import("std");
    const expect = std.testing.expect;
    const eql = std.mem.eql;
    test "hello world" {
        const out_file = std.io.getStdOut();
        try out_file.writer().print(
            "Hello, {s}!\n",
            .{"World"},
        );
    }
    
  • Format Specifiers :

    • {s} : strings.

    • {d} : decimal.

      • {d:.2}

    • {c} : ascii character.

    • {*} : pointer formatting, printing the address rather than the value.

    • {any} : default formatting.

    • {e} : floats in scientific notation.

    • {b} : binary.

    • {o} : octal.

    • etc.

Functions

  • Functions .

  • All function arguments are immutable - if a copy is desired the user must explicitly make one.

  • Unlike variables, which are snake_case, functions are camelCase.

fn addFive(x: u32) u32 {
    return x + 5;
}

test "function" {
    const y = addFive(0);
}
Built-in Functions

Control Flow (if, while, for, switch, labelled, iterators)

If
const a = true;
var x: u16 = 0;
if (a) {
    x += 1;
} else {
    x += 2;
}
const a = true;
var x: u16 = 0;
x += if (a) 1 else 2;
  • "If it exists" :

    // Shortcut for "if (x) x else 0"
    var value = x orelse 0;
    
    // Get a pointer to the value (if it exists).
    if (a) |*value| { value.* += 1; }
    
While
var i: u8 = 2;    
while (i < 100) {        
    i *= 2;    
}
// Simple "while" loop.
while (i < 10) { i += 1; }

// While loop with a "continue expression"
// (expression executed as the last expression of the loop).
while (i < 10) : (i += 1) { ... }
// Same, with a more complex continue expression (block of code).
while (i * j < 2000) : ({ i *= 2; j *= 3; }) { ... }
var sum: u8 = 0;
var i: u8 = 1;
while (i <= 10) : (i += 1) {
    sum += i;
}
var sum: u8 = 0;
var i: u8 = 0;
while (i <= 3) : (i += 1) {
    if (i == 2) continue;
    sum += i;
}
var sum: u8 = 0;
var i: u8 = 0;
while (i <= 3) : (i += 1) {
    if (i == 2) break;
    sum += i;
}
  • Loops as Expressions :

    • Like return , break  accepts a value.

    • This can be used to yield a value from a loop.

    • Loops in Zig also have an else  branch, which is evaluated when the loop is not exited with a break .

    fn rangeHasNumber(begin: usize, end: usize, number: usize) bool {
        var i = begin;
        return while (i < end) : (i += 1) {
            if (i == number) {
                break true;
            }
        } else false;
    }
    
    test "while loop expression" {
        try expect(rangeHasNumber(0, 10, 3));
    }
    
For
// We've had to assign values to `_`, as Zig does not allow us to have unused values.
// Character literals are equivalent to integer literals
const string = [_]u8{ 'a', 'b', 'c' };

for (string, 0..) |character, index| {
    _ = character;
    _ = index;
}

for (string) |character| {
    _ = character;
}

for (string, 0..) |_, index| {
    _ = index;
}

for (string) |_| {}
// To iterate over a portion of a slice, reslice.
for (items[0..1]) |value| { sum += value; }

// Loop over every item of an array (or slice).
for (items) |value| { sum += value; }

// Iterate and get pointers on values instead of copies.
for (items) |*value| { value.* += 1; }

// Iterate with an index.
for (items) |value, i| { print("val[{}] = {}\n", .{i, value}); }

// Iterate with pointer and index.
for (items) |*value, i| { print("val[{}] = {}\n", .{i, value}); value.* += 1; }
// Break and continue are supported.
for (items) |value| {
    if (value == 0)  { continue; }
    if (value >= 10) { break;    }
    // ...
}
// For loops can also be used as expressions.
// Similar to while loops, when you break from a for loop,
// the else branch is not evaluated.
var sum: i32 = 0;
// The "for" loop has to provide a value, which will be the "else" value.
const result = for (items) |value| {
    if (value != null) {
        sum += value.?; // "result" will be the last "sum" value.
    }
} else 0;                  // Last value.
Switch
  • Safety :

    • The types of all branches must coerce to the type which is being switched upon. All possible values must have an associated branch - values cannot be left out. It is exhaustive.

  • Zig's switch  works as both a statement and an expression.

    • Statement :

      const expect = @import("std").testing.expect;
      
      test "switch statement" {
          var x: i8 = 10;
          switch (x) {
              -1...1 => {
                  x = -x;
              },
              10, 100 => {
                  //special considerations must be made
                  //when dividing signed integers
                  x = @divExact(x, 10);
              },
              else => {},
          }
          try expect(x == 1);
      }
      
    • Expression :

      const expect = @import("std").testing.expect;
      
      test "switch expression" {
          var x: i8 = 10;
          x = switch (x) {
              -1...1 => -x,
              10, 100 => @divExact(x, 10),
              else => x,
          };
          try expect(x == 1);
      }
      
  • Cases cannot fall through to other branches.

Labelled
  • Blocks

    • The value of an empty block {}  is a value of the type void .

    const expect = @import("std").testing.expect;
    
    test "int-float conversion" {
        const a: i32 = 0;
        const b = @as(f32, @floatFromInt(a));
        const c = @as(i32, @intFromFloat(b));
        try expect(c == a);
    }
    
    • This can be seen as being equivalent to C's i++ .

    blk: {
        const tmp = i;
        i += 1;
        break :blk tmp;
    }
    
  • Loops :

    • Loops can be given labels, allowing you to break  and continue  to outer loops.

    test "nested continue" {
        var count: usize = 0;
        outer: for ([_]i32{ 1, 2, 3, 4, 5, 6, 7, 8 }) |_| {
            for ([_]i32{ 1, 2, 3, 4, 5 }) |_| {
                count += 1;
                continue :outer;
            }
        }
        try expect(count == 8);
    }
    
Iterators

Imports

  • The built-in function @import  takes in a file, and gives you a struct type based on that file.

  • All declarations labelled as pub  (for public) will end up in this struct type, ready for use.

  • @import("std")  is a special case in the compiler, and gives you access to the standard library.