Writing footnotes in Zig

I just occurred to me that we can use the void type to create footnotes:

const std = @import("std");

const cURL = @cImport({
    @cInclude("curl/curl.h");
});

pub fn main() !void {
    var arena_state = std.heap.ArenaAllocator.init(std.heap.c_allocator);
    defer arena_state.deinit();

    const allocator = arena_state.allocator();

    if (cURL.curl_global_init(cURL.CURL_GLOBAL_ALL) != cURL.CURLE_OK)
        return error.CURLGlobalInitFailed;
    defer cURL.curl_global_cleanup();
    @"1";

    const handle = cURL.curl_easy_init() orelse return error.CURLHandleInitFailed;
    defer cURL.curl_easy_cleanup(handle);
    @"2";

    var response_buffer = std.ArrayList(u8).init(allocator);

    defer response_buffer.deinit();
    @"3";

    if (cURL.curl_easy_setopt(handle, cURL.CURLOPT_URL, "https://ziglang.org") != cURL.CURLE_OK)
        return error.CouldNotSetURL;
    @"4";

    if (cURL.curl_easy_setopt(handle, cURL.CURLOPT_WRITEFUNCTION, writeToArrayListCallback) != cURL.CURLE_OK)
        return error.CouldNotSetWriteCallback;
    if (cURL.curl_easy_setopt(handle, cURL.CURLOPT_WRITEDATA, &response_buffer) != cURL.CURLE_OK)
        return error.CouldNotSetWriteCallback;
    @"5";

    if (cURL.curl_easy_perform(handle) != cURL.CURLE_OK)
        return error.FailedToPerformRequest;
    @"6";

    std.log.info("Got response of {d} bytes", .{response_buffer.items.len});
    std.debug.print("{s}\n", .{response_buffer.items});
}

fn writeToArrayListCallback(data: *anyopaque, size: c_uint, nmemb: c_uint, user_data: *anyopaque) callconv(.C) c_uint {
    var buffer: *std.ArrayList(u8) = @ptrCast(@alignCast(user_data));
    var typed_data: [*]u8 = @ptrCast(data);
    buffer.appendSlice(typed_data[0 .. nmemb * size]) catch return 0;
    return nmemb * size;
}

const @"1" = {
    // global curl init, or fail
};

const @"2" = {
    // curl easy handle init, or fail
};

const @"3" = {
    // superfluous when using an arena allocator, but
    // important if the allocator implementation changes
};

const @"4" = {
    // setup curl options
};

const @"5" = {
    // set write function callbacks
};

const @"6" = {
    // perform
};

In VS Code, the footnote will pop up when you mouse over the number:

Kinda neat. Not sure what scenario where this would be useful.

12 Likes

I guess, if you want to add comment but also don’t want to add comment but also want the comment to obstruct the commented code..?


..but seriously, neat trick, but as you said no idea about real usefulness, though..

5 Likes

Cool.
This pattern makes comments ‘reusable and composable.’ I might be willing to use it when my code repeatedly uses a certain technique that needs explanation across multiple API implementations in relevant contexts. It’s a bit pitty that it cannot compose documentation.

2 Likes

I like it. But the downside is that the person reading the code may not be using a LSP.

2 Likes

The example I provided wasn’t very good. I just randomly picked an example from the doc. A better application of the idea is the default build.zig produced by the zig init command:

const std = @import("std");

pub fn build(b: *std.Build) void { @"1";
    const target = b.standardTargetOptions(.{}); @"2";
    const optimize = b.standardOptimizeOption(.{}); @"3"; @"4";

    const mod = b.addModule("test_proj", .{
        .root_source_file = b.path("src/root.zig"),
        .target = target,
    }); @"5";

    const exe = b.addExecutable(.{
        .name = "test_proj",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "test_proj", .module = mod },
            },
        }),
    }); @"6";

    b.installArtifact(exe); @"7";

    const run_step = b.step("run", "Run the app"); @"8";

    const run_cmd = b.addRunArtifact(exe);
    run_step.dependOn(&run_cmd.step); @"9";

    run_cmd.step.dependOn(b.getInstallStep()); @"10";

    if (b.args) |args| {
        run_cmd.addArgs(args);
    } @"11";

    const mod_tests = b.addTest(.{
        .root_module = mod,
    }); @"12";

    const run_mod_tests = b.addRunArtifact(mod_tests); @"13";

    const exe_tests = b.addTest(.{
        .root_module = exe.root_module,
    }); @"14";

    const run_exe_tests = b.addRunArtifact(exe_tests); @"14";

    const test_step = b.step("test", "Run tests");
    test_step.dependOn(&run_mod_tests.step);
    test_step.dependOn(&run_exe_tests.step); @"15"; @"16";
}

Moving the lengthy comments to the bottom means that the file can actually be used as-is, without first undergoing a major clean-up. The code here of course assumes that auto-formatting would be adjusted so that the numeric labels don’t receive a line-feed.

1 Like

<ESC>:g,//,d<CR>

There’s no mouse-over in books either and we do fine :slight_smile:. That’s for quick refreshers anyway. When you actually need to study the text, a side-by-side view would make more sense:

You don’t need an LSP for that.

True. I just wouldn’t like it at all myself, and I mistakenly assumed that nobody would.

Actually a use case I first had in mind was code for students. With the small comments you could just insert them in the code, but if someone really need large texts then it starts to make sense.

Or maybe something like auditable test code where you need to track relations between tests and relevant specs ..although there might be better way.