NUS Hackers Wiki
NUS Hackers Wiki
  • NUS Hackers Wiki
  • Hackerschool
    • Virtual Machines and Linux
    • Beginners' Guide to the Terminal
      • Introduction to the Terminal
      • Modern Shell Tools
      • Shell Scripting
      • Real World Scripting
      • Resources
    • Self-Hosting: Three Easy Pieces
      • 1. Setting up your server
      • 2. Running Services
      • 3. Monitoring your server
    • Vim
    • Introduction to Zig
      • Language Basics
      • Error Handling
      • Memory Management
      • Working with C
      • Exploring comptime
    • CI/CD with Github Actions
      • Background
      • Basics of Github Actions
        • Target workflow
        • Running unit tests
        • Linting code
        • Deploying to Github Pages
      • Advanced use cases
        • Pollers
        • Github script
        • Executing third-party scripts
        • Reusable workflows
      • Cookbook
    • Lightning Git
      • Git Concepts
      • Getting Started with Git
      • Making your first commit
      • Branching
      • Merge Conflicts
      • Integrating remote repositories
      • Collaborative Workflows
      • Commit Manipulation and Reflog
      • Interactive rebasing
      • filter-repo
  • Orbital
    • JavaScript
      • Browser Developer Tools
      • Getting Started
      • Datatypes
      • Operators and Operations
      • Loops and Conditions
      • Functions
      • Strings
      • Arrays
      • HTML
        • Getting Started
        • Tag Attributes
        • HTML Forms
        • Browser Inspector
      • CSS
        • Selectors
        • Colors in CSS
        • Measurements in CSS
        • The Box Model
        • Adding Styles - Part 1
        • Adding Styles - Part 2
      • Working with the DOM
        • Querying the DOM - Selectors
        • Querying the DOM - Element Attributes
        • Querying the DOM - Element Styles
        • Events with JS and HTML
        • Exercise: Click Counter
        • Editing the DOM
        • Fetch Requests
        • Exercise: The NUSMods API
    • React
      • Setup
      • State
    • React Native
      • Setup
      • Intro to JSX
      • Basic Syntax
      • Handling UI
      • Props
      • State Management
    • Git
      • Setup
      • Command Glossary
      • Fundamental Concepts
        • Getting Started
        • Integrating Remote Repositories
        • Branching
        • Merge Conflicts
      • Collaborative Workflows
        • Fork and PR Workflow
        • Branch and PR Workflow
      • Advanced Concepts
        • Ignoring Files
        • Commit Message Conventions
        • Github Collaborators
        • CI/CD with Github Actions
        • Advanced Git Commands
      • FAQ
    • Telegram Bot
      • Creating a TeleBot
      • API Calls
      • Telebot Basics
      • Integrating API's
    • Relational Database
      • Database Overview
      • Database Design
      • Entity Relationship Diagram
      • SQL Basics & PostgreSQL
    • TypeScript
      • Types and Interfaces
      • Utility Types
      • Typing Component Props, Events, and Hooks
      • Why You Should Avoid Using any (and What to Do Instead)
      • TypeScript Tricks You’ll Use All the Time in React
Powered by GitBook
On this page
  • What is comptime?
  • Pre-computing values
  • Generics
Edit on GitHub
Export as PDF
  1. Hackerschool
  2. Introduction to Zig

Exploring comptime

Let's create a sum function that takes in a slice of numbers (any type of number!!) and returns the sum of all the numbers! How can we do that?

heheh i see you Java/C++ programmers reaching for your < and > hehehe

const std = @import("std");

fn sum(comptime T: type, values: []const T) T {
    var result: T = 0;
    for (values) |value| {
        result += value;
    }
    return result;
}

pub fn main() void {
    const some_i32s = [_]i32{ 1, 2, 3, 4, 5 };
    std.debug.print("sum of i32s: {}\n", .{sum(i32, &some_i32s)});

    const some_f32s = [_]f32{ 1.0, 2.0, 3.0, 4.0, 5.0 };
    std.debug.print("sum of f32s: {}\n", .{sum(f32, &some_f32s)});

    const some_u64s = [_]u64{ 1, 2, 3, 4, 5 };
    std.debug.print("sum of u64s: {}\n", .{sum(u64, &some_u64s)});
}

Woah, what is comptime? And why is the type of T like type itself?

What is comptime?

I'm sure many of us have heard of macros. They exist in languages like C, Rust or even Lisp (in quite a different form), and they serve as a way of executing some code at compile-time instead of runtime.

We might have also heard of generics in Java and Rust, or even templates in C++ (not sure if I'm committing a sin to lump these together), in order to write code that works across all types with certain constraints.

If you're a user of Go, you might've also used go generate to write repeated code for you.

Well, Zig has a solution that encompasses all three use-cases mentioned above, and that is comptime! What the comptime feature in Zig allows you to do, is simply write Zig code (not any other special language ala C++ template metaprogramming) that is executed at compile-time, instead of runtime!

Pre-computing values

Looking at our earlier example, our sum function is pure, and can be pre-computed. Let's try to get Zig to precompute the results instead of computing the results at runtime.

Of course, the Zig compiler might have also realised this and done the optimisation already, but for the sake of this being an example of pre-computing values, let's treat this as an optimisation!

const std = @import("std");

fn sum(comptime T: type, values: []const T) T {
    var result: T = 0;
    for (values) |value| {
        result += value;
    }
    return result;
}

pub fn main() void {
    const some_i32s = [_]i32{ 1, 2, 3, 4, 5 };
    std.debug.print("sum of i32s: {}\n", .{comptime sum(i32, &some_i32s)});

    const some_f32s = [_]f32{ 1.0, 2.0, 3.0, 4.0, 5.0 };
    std.debug.print("sum of f32s: {}\n", .{comptime sum(f32, &some_f32s)});

    const some_u64s = [_]u64{ 1, 2, 3, 4, 5 };
    std.debug.print("sum of u64s: {}\n", .{comptime sum(u64, &some_u64s)});
}

Wow! Do you notice what changed? By simply adding the comptime keyword in front of our call to sum, the function was called at compile-time instead of runtime.

Generics

Our sum function is already a pretty good example of using comptime to create the effect of generics. Let's go one step further and create a data structure that supports generics.

const std = @import("std");

fn Vector2D(comptime T: type) type {
    return struct {
        x: T,
        y: T,
    };
}

pub fn main() void {
    const vector_of_i32s = Vector2D(i32){ .x = 1, .y = 2 };
    const vector_of_f32s = Vector2D(f32){ .x = 1.0, .y = 2.0 };

    std.debug.print("vector_of_i32s: {any}\n", .{vector_of_i32s});
    std.debug.print("vector_of_f32s: {any}\n", .{vector_of_f32s});
}

Notice how we can return structs from functions in Zig! This definitely wouldn't work at runtime, since types don't have a representation at runtime (unless we use runtime reflection). So Zig is actually running the Vector2D function at comptime here, and treating the resulting structs as types to be constructed.

Remember that this is all Zig code, which means we can go one step further and make the length generic as well!

const std = @import("std");

fn Vector(comptime T: type, comptime len: usize) type {
    return struct {
        values: [len]T,
    };
}

const Vector_i32_2D = Vector(i32, 2);
const Vector_f32_4D = Vector(f32, 4);

pub fn main() void {
    const vector_of_2_i32s = Vector_i32_2D{ .values = [_]i32{ 1, 2 } };
    const vector_of_4_f32s = Vector_f32_4D{ .values = [_]f32{ 1.0, 2.0, 3.0, 4.0 } };

    std.debug.print("vector_of_2_i32s: {any}\n", .{vector_of_2_i32s});
    std.debug.print("vector_of_4_f32s: {any}\n", .{vector_of_4_f32s});
}

Here, both parameters to Vector play a part in defining what kind of struct the resulting type will be!

PreviousWorking with CNextCI/CD with Github Actions

Last updated 3 months ago