# 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?

{% hint style="info" %}
*heheh i see you Java/C++ programmers reaching for your < and > hehehe*
{% endhint %}

```zig
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.

{% hint style="info" %}
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!
{% endhint %}

```zig
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.

```zig
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 `struct`s 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!

```zig
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!
