Introduction to Zig

Abstract

  • Why learn programming languages
  • Set up development environment
  • Zig Overview
  • Free practice
  • Unleash you on the rest of Zig Day attendees

Why Learn PLs? 

  • Look behind the curtain
  • Anyone can create one
  • Creators imprint upon thier languages
  • New languages can teach you new ways of thinking

Why Learn Zig?

  • Cross-compiling toolchain
  • "Simple"
  • Optimize for reading over writing
  • Designed to build high quality software, not great for quick scripts

Toolchain

  • Clang is embedded in the Zig compiler
  • Zig can cross-compile C
  • Zig can directly import C
  • Zig build system is written in Zig

"Simple" Language

  • Small enough to fit in your head
  • "If everyone had their favourite feature from C++, we'd end up with C++" - Andrew Kelley
  • Features tend to work nicely together

Programming Mindset

  • Be explicit
  • Leave trip wires to catch incorrectness
  • Document assumptions using assertions
  • Incorrect program should crash and provide information

Dev Environment

Hello World

  1. Create new directory
  2. Run `zig init`
  3. Inspect generated files
$ zig build run
$ zig build test

Integer Overflow

const std = @import("std");

fn add(a: u8, b: u8) u8 {
    return a + b;
}

pub fn main() void {
    const b = add(200, 200);
    
    // Note, Zig doesn't allow unused variables
    // This says "yes I know, but do it anyways"
    _ = b;
}

Save this to a file ending with .zig, then run:

$ zig run ./integer_overflow.zig

Zig Overview

Purpose

  • Show you how the features layer
  • Not exhaustive
  • If you don't get something, that's fine, you'll get to revisit in detail later

Integers

// Signedness
const a: u32 = 42;
const b: i32 = -560;

// Arbitrary bit size
const c: u3 = 7;

// Wrapping and saturating operators
const d = a %+ 200;
const e = b |+ -5000;

Functions

fn double(a: u32) u32 {
	return 2 * a;
}

pub fn main() void {
	// This won't compile. Return values 
    // MUST be handled somehow
	double(4);
    const c = double(2);
    _ = c;
}

If Else

fn my_func(flag: bool, num: u16) void {
    if (flag) {
        // Do thing if flag is true...

        // Whatever is in the parenthesis MUST result to a boolean, integers
        // and pointers do not implicitly convert, you must explicitly compare
        // it.
    } else if (num != 0) {
        // This happens if ptr is null, and flag is false
    } else {
        // do other thing...
    }
}

If/Else as an Expression

fn special_case(n: u32) u32 {
    // in C, this would be: (n % 2 == 0) ? 1 : n;
    const a = if (n % 2 == 0) 1 else n;
    return 2 * a;
}

While Loop

pub fn main() void {
    var acc: u32 = 0;

    var i: u32 = 0;
    while (i < 10) {
        acc += i;
        i += 1;
    }

    // Alternatively:
    while (i < 20) : (i += 1) {
        acc += i;
    }
}

For Loop

var array1: [10]u8 = undefined;
var array2: [10]u8 = undefined;

fn main() void {
    // Fixed range iteration
    for (0..10) {
        // ...
    }

    // Iterate array
    for (array1) |value| {
        //        ^ "capture" value
    }

    // Get an index alongside value
    for (array1, 0..) |value, i| {
        //       ^ Can be a different start offset
        //         Eg. 3 would effectively be 3..13
    }

    // Arrays/slices of the same length can be looped side-by-side
    for (array1, array2) |value1, value2| {
        // ...
    }
}

Loop as an Expression

var array: [5]u8 = .{1, 2, 3, 7, 5};

fn main() void {
    // Find the index of the even number in the array
    const index = for (array, 0..) |value, i| {
        if (value % 2 == 0)
            break i;
    } else 0; // This happens if we never break from the loop

    _ = index;
}

Switch

fn is_printable(char: u8) bool {
    switch (char) {
        // Single match
        0x21 => return true,
        // List matches
        0x20, 0x22 => return true,
        // Exclusive range
        0x21..0x30 => return true,
        // Inclusive range
        0x30...0x7E => return true,
        // "default" in C
        else => return false,
    }
}

Switch as an Expression

fn is_printable(char: u8) bool {
    return switch (char) {
        0x20 ... 0x7E => true,
        else => false,
    }
}

Enums

// Types are values too
const Color = enum {
    red,
    blue,
    green,
};

fn is_warm(color: Color) bool {
    // .red is an "anonymous" enum literal. The compiler ensures that it's part
    // of Color at compile-time
    return color == .red;
}

// You can switch on enums
pub fn is_cool(color: Color) bool {
    return switch(color) {
        .green, .blue => true,
        .red => false,
    };
}

Errors

// This is an "error set", it's like an enum, but is handled differently in
// different contexts.
const OpenError = error {
    DoesntExist,
    Denied,
};

fn open_file() OpenError!u32 {
    if (some_flag) {
        // Error literals start with "error."
        return error.Denied;
    }

    // Just a value to fake a file handle
    return 0;
}

Errors Continued

// main can return errors too. anyerror is a special error set that's all error
// sets in the program combined into one.
pub fn main() anyerror!void {
    // You can "catch" an error, and return a default value that matches the
    // "value" type
    const handle = open_file() catch 1;
    // catch an error, capture the exact error in 'err', then return error from main
    const handle = open_file() catch |err| return err;
    // A shorter version of the above, very common:
    const handle = try open_file();
    // Big surprise, you can switch on errors:
    const handle = open_file() catch |err| switch (err) {
        error.DoesntExist => {
            // do stuff
        },
        error.Denied => {
            // do something else
        },
    };
}

Pointers

pub fn main() void {
    var i: u32 = 4;
    var buf: [80]u8 = undefined;

    // pointer to single item
    const j: *u32 = &i;
    // pointer to many
    const k: [*]u8 = &buf;
    // optionality
    const l: ?*i16 = null;
    // sentinel, here is the equivalent of a C string literal:
    const c_str: ?[*:0]const u8 = null;

    // pointers can be dereferenced with .*:
    const m = j.*;
}

Optionals

// Optionality can be applied to any type, even non-pointers.
fn maybe_do_thing(num: ?u32) {
    // optionals have to be unwrapped, the explicit way is:
    if (num) |n| {
        // do thing...
    }

    // If I KNOW it's not null, I can "dereference" it:
    const m = num.?;
}

Structures

// Bundle primitive types into a more complex type:
const Player = struct {
    x: u32,
    y: u32,
};

pub fn main() void {
    // This is what it looks like to initialize a struct:
    var main_player = Player{
        .x = 2,
        .y = 4,
    };

    // fields can be accessed with .
    main_player.x = 2;
}

Methods

const Player = struct {
    x: u32,
    y: u32,

    // A function nested in the struct, and takes the struct the first argument
    // can be called as a method.
    fn move_forward(player: *Player, count: u32) void {
        player.y += count;
    }
};

pub fn main() void {
    // This is what it looks like to initialize a struct:
    var main_player = Player{
        .x = 2,
        .y = 4,
    };

    main_player.move_forward(5);

    // It's just syntactic sugar, equivalently:
    Player.move_forward(&player, 5);

}

comptime

const std = @import("std");

// Generics can be acheived with "comptime".
fn max(comptime T: type, a: T, b: T) T {
    // Types are values, and can be used as such.
    if (T == bool) {
        return a or b;
    } else if (a > b) {
        return a;
    } else {
        return b;
    }
}

pub fn main() void {
    _ = max(u32, 1, 10);
    _ = max(bool, true, true);
}

comptime

const std = @import("std");

// Functions can return types
fn List(comptime T: type) type {
    return struct {
        items: []T,
        len: usize,
    };
}

// Type alias
const IndexList = List(u32);

fn fibonacci(index: u32) u32 {
    if (index < 2) return index;
    return fibonacci(index - 1) + fibonacci(index - 2);
}

pub fn main() void {
    var buf: [10]u32 = undefined;

    var list: IndexList = .{
        .items = &buf,
        .len = 0,
    };

    // Execution of a function at compile-time can be enforced:
    const n = comptime fibonacci(100);
    _ = n;
}

Where to Go From Here

Zig Communities

Resources

Congratulations, we all made it

Introduction to Zig

By Matt Knight

Introduction to Zig

  • 11