365 Days of Code - Day 036

Project Status

ProjectLanguageStatusDue DateLatest Update
Personal WebsiteHugoOngoingNoneThe site is live. There are some TODOs. Need to work on categorization, tagging, and layout improvements.
Laravel From ScratchLaravel (PHP)In-Progress2026-03-31Episode 8
PRMLaravel (PHP)In-Progress2026-03-31Working alongside other Laravel projects.
Client Website (J.L.)Laravel (PHP)In-Progress2026-03-31Working alongside other Laravel projects.
Project EulerCOngoingNoneWorking on P25. BigInt (AI gen) was a waste of time, need to rewrite
Practice JavaJavaPausedNoneInstalled, need to find a good project.
Practice PythonPythonPausedNoneInstalled, need to find a good project.
Learn GoGoPausedNoneInstalled, work on LDAP Injector from ippsec.
Learn RustRustHaven’t StartedNoneInstalled, will try network protocols after finishing in C and Zig.
Learn ElixirElixirHaven’t StartedNoneInstalled, need a good tutorial project.
Learn HaskellHaskellHaven’t StartedNoneInstalled, need a good tutorial project.
Learn ZigZigHaven’t StartedNoneInstalled, will try network protocols after finishing in C.
Linux+N/AIn-Progress2026-03-31Reading Chapter 4.
Cyber Quest 2026N/AIn-Progress2026-02-28Finished quiz 1 with 75%. Need to work on ARP poisoning and timestamp adjustments in WireShark.
Operating SystemsN/AIn-Progress2026-03-31Reading Chapter 4: Abstraction
Grey-Hat HackingVariousIn-Progress2026-03-31Reading Chapter 8: Threat Hunting Lab
PHP Time TrackerPHPBeta FinishedNoneWorking on a basic level. Could use a couple more updates to make it fully functional.
HTTP Status Code ReaderCComplete2026-02-18Complete. Could potentially upgrade for more advanced functions or follow redirects.
ZSH Configurationbash/zshCompleteNoneSort of an ongoing process, but complete for now. Works good.
Network ProtocolsCIn-ProgressNoneIPv4 Datagram Header built. Need to work on payload and ICMP.

Zig

Taking a break from the network protocol to try and start with with Zig (opens in a new tab). I was researching other languages to try writing the network stack with, and I came across Zig as a compelling option to try in addition to Rust. I had some issues writing a simple hello program, but Zig works fine with zig init. The code produced for a simple hello world program is pretty verbose compared to other languages. I tried a much simpler version but kept having library issues.

The standard library master documentation provided a simple hello program (opens in a new tab), but I could not get it to compile. I’m realizing now that the documentation I was reading was likely using an older version of the package.

This is what I tried:

  • hello.zig
zig
const std = @import("std");

pub fn main(init: std.process.Init) !void {
    try std.fs.File.stdout().writeStreamingAll(init.io, "Hello, World!\n");
}
1
2
3
4
5
const std = @import("std");

pub fn main(init: std.process.Init) !void {
    try std.fs.File.stdout().writeStreamingAll(init.io, "Hello, World!\n");
}
bash
> zig build-exe hello.zig
hello.zig:4:16: error: root source file struct 'fs' has no member named 'File'
    try std.fs.File.stdout().writeAll("Hello, Zig!\n");
               ^~~~
/opt/zig/zig-x86_64-linux-0.16.0-dev.2565+684032671/lib/std/fs.zig:1:1: note: struct declared here
//! File System.
^~~~~~~~~~~~~~~~
referenced by:
    callMain [inlined]: /opt/zig/zig-x86_64-linux-0.16.0-dev.2565+684032671/lib/std/start.zig:677:59
    callMainWithArgs [inlined]: /opt/zig/zig-x86_64-linux-0.16.0-dev.2565+684032671/lib/std/start.zig:629:20
    posixCallMainAndExit: /opt/zig/zig-x86_64-linux-0.16.0-dev.2565+684032671/lib/std/start.zig:582:38
    2 reference(s) hidden; use '-freference-trace=5' to see all references
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
> zig build-exe hello.zig
hello.zig:4:16: error: root source file struct 'fs' has no member named 'File'
    try std.fs.File.stdout().writeAll("Hello, Zig!\n");
               ^~~~
/opt/zig/zig-x86_64-linux-0.16.0-dev.2565+684032671/lib/std/fs.zig:1:1: note: struct declared here
//! File System.
^~~~~~~~~~~~~~~~
referenced by:
    callMain [inlined]: /opt/zig/zig-x86_64-linux-0.16.0-dev.2565+684032671/lib/std/start.zig:677:59
    callMainWithArgs [inlined]: /opt/zig/zig-x86_64-linux-0.16.0-dev.2565+684032671/lib/std/start.zig:629:20
    posixCallMainAndExit: /opt/zig/zig-x86_64-linux-0.16.0-dev.2565+684032671/lib/std/start.zig:582:38
    2 reference(s) hidden; use '-freference-trace=5' to see all references

But, checking the master documentation provided a different method. The fs was changed to io.

zig
const std = @import("std");

pub fn main(init: std.process.Init) !void {
    try std.Io.File.stdout().writeStreamingAll(init.io, "Hello, World!\n");
}
1
2
3
4
5
const std = @import("std");

pub fn main(init: std.process.Init) !void {
    try std.Io.File.stdout().writeStreamingAll(init.io, "Hello, World!\n");
}

This might work, but instead I went with the zig init procedure.

By default, it seems the Zig init uses the name of the root directory for the program. To craft the initial hello world program, these steps were taken:

bash
mkdir hello-world
cd hello-world
zig init
zig build run
1
2
3
4
mkdir hello-world
cd hello-world
zig init
zig build run
  • src/main.zig
zig
const std = @import("std");
const Io = std.Io;

const hello_world = @import("hello_world");

pub fn main(init: std.process.Init) !void {
    // Prints to stderr, unbuffered, ignoring potential errors.
    std.debug.print("All your {s} are belong to us.\n", .{"codebase"});

    // This is appropriate for anything that lives as long as the process.
    const arena: std.mem.Allocator = init.arena.allocator();

    // Accessing command line arguments:
    const args = try init.minimal.args.toSlice(arena);
    for (args) |arg| {
        std.log.info("arg: {s}", .{arg});
    }

    // In order to do I/O operations need an `Io` instance.
    const io = init.io;

    // Stdout is for the actual output of your application, for example if you
    // are implementing gzip, then only the compressed bytes should be sent to
    // stdout, not any debugging messages.
    var stdout_buffer: [1024]u8 = undefined;
    var stdout_file_writer: Io.File.Writer = .init(.stdout(), io, &stdout_buffer);
    const stdout_writer = &stdout_file_writer.interface;

    try hello_world.printAnotherMessage(stdout_writer);

    try stdout_writer.flush(); // Don't forget to flush!
}

test "simple test" {
    const gpa = std.testing.allocator;
    var list: std.ArrayList(i32) = .empty;
    defer list.deinit(gpa); // Try commenting this out and see if zig detects the memory leak!
    try list.append(gpa, 42);
    try std.testing.expectEqual(@as(i32, 42), list.pop());
}

test "fuzz example" {
    const Context = struct {
        fn testOne(context: @This(), input: []const u8) anyerror!void {
            _ = context;
            // Try passing `--fuzz` to `zig build test` and see if it manages to fail this test case!
            try std.testing.expect(!std.mem.eql(u8, "canyoufindme", input));
        }
    };
    try std.testing.fuzz(Context{}, Context.testOne, .{});
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
const std = @import("std");
const Io = std.Io;

const hello_world = @import("hello_world");

pub fn main(init: std.process.Init) !void {
    // Prints to stderr, unbuffered, ignoring potential errors.
    std.debug.print("All your {s} are belong to us.\n", .{"codebase"});

    // This is appropriate for anything that lives as long as the process.
    const arena: std.mem.Allocator = init.arena.allocator();

    // Accessing command line arguments:
    const args = try init.minimal.args.toSlice(arena);
    for (args) |arg| {
        std.log.info("arg: {s}", .{arg});
    }

    // In order to do I/O operations need an `Io` instance.
    const io = init.io;

    // Stdout is for the actual output of your application, for example if you
    // are implementing gzip, then only the compressed bytes should be sent to
    // stdout, not any debugging messages.
    var stdout_buffer: [1024]u8 = undefined;
    var stdout_file_writer: Io.File.Writer = .init(.stdout(), io, &stdout_buffer);
    const stdout_writer = &stdout_file_writer.interface;

    try hello_world.printAnotherMessage(stdout_writer);

    try stdout_writer.flush(); // Don't forget to flush!
}

test "simple test" {
    const gpa = std.testing.allocator;
    var list: std.ArrayList(i32) = .empty;
    defer list.deinit(gpa); // Try commenting this out and see if zig detects the memory leak!
    try list.append(gpa, 42);
    try std.testing.expectEqual(@as(i32, 42), list.pop());
}

test "fuzz example" {
    const Context = struct {
        fn testOne(context: @This(), input: []const u8) anyerror!void {
            _ = context;
            // Try passing `--fuzz` to `zig build test` and see if it manages to fail this test case!
            try std.testing.expect(!std.mem.eql(u8, "canyoufindme", input));
        }
    };
    try std.testing.fuzz(Context{}, Context.testOne, .{});
}
  • src/root.zig
zig
//! By convention, root.zig is the root source file when making a package.
const std = @import("std");
const Io = std.Io;

/// This is a documentation comment to explain the `printAnotherMessage` function below.
///
/// Accepting an `Io.Writer` instance is a handy way to write reusable code.
pub fn printAnotherMessage(writer: *Io.Writer) Io.Writer.Error!void {
    try writer.print("Run `zig build test` to run the tests.\n", .{});
}

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

test "basic add functionality" {
    try std.testing.expect(add(3, 7) == 10);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
//! By convention, root.zig is the root source file when making a package.
const std = @import("std");
const Io = std.Io;

/// This is a documentation comment to explain the `printAnotherMessage` function below.
///
/// Accepting an `Io.Writer` instance is a handy way to write reusable code.
pub fn printAnotherMessage(writer: *Io.Writer) Io.Writer.Error!void {
    try writer.print("Run `zig build test` to run the tests.\n", .{});
}

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

test "basic add functionality" {
    try std.testing.expect(add(3, 7) == 10);
}

Another language to learn added to the list!