Zig Notes

zig q&a
Login

zig q&a

  1. How can I mutate a string literal?
  2. Can I detect if a value is undefined?
  3. Does Zig support multiple value returns, like Go and Lua?
  4. How can you neatly check multiple options/errors at the same time?
  5. Does Zig have Closures?

How can I mutate a string literal?

c.f. zig.news, and incidental demonstration in Type Coercion

String literals are compiled such that the literal, at runtime, is loaded into read-only memory. When C does this, it's easy to discover with a runtime error:

#include <stdio.h>

int main() {
	char *s1 = "Bob";
	s1[0] = 'J';
	puts(s1);
	return 0;
}
$ gcc -o mutstr -Wall -Wextra mutstr.c 
$ ./mutstr 
Segmentation fault (core dumped)

Zig tries to be a little bit nicer and tell you before runtime, but handles the memory similarly:

const std = @import("std");

test "mutable string literals (bad)" {
    var s1 = "Bob";
    s1[0] = 'J';
    std.debug.print("s1: {s}\n", .{s1});
}

test "mutable string literals (good)" {
    const s1 = "Bob";
    var s2 = "Bob".*;
    s2[0] = 'J';
    std.debug.print("s1: {s} {}\n", .{ s1, @TypeOf(s1) });
    std.debug.print("s2: {s} {}\n", .{ s2, @TypeOf(s2) });
}
$ zig test mutstr.zig --test-filter bad 
mutstr.zig:5:7: error: cannot assign to constant
    s1[0] = 'J';
    ~~^~~

To get a mutable string you can copy the string to an array, which can be done most conveniently with a dereference, since the string is a pointer to an array:

$ zig test mutstr.zig --test-filter good
s1: Bob *const [3:0]u8
s2: Job [3:0]u8
All 1 tests passed.

Can I detect if a value is undefined?

At runtime, no.

At compile-time, yes (zig discord link):

// by mlugg

fn isUndefined(comptime x: anytype) bool {
    // struct{comptime T = foo}
    const struct_name = @typeName(@TypeOf(.{x}));
    // T
    const type_name = @typeName(@TypeOf(x));

    const prefix_len = "struct{comptime ".len + type_name.len + " = ".len;
    const suffix_len = "}".len;
    const val_name = struct_name[prefix_len .. struct_name.len - suffix_len];
    return @import("std").mem.eql(u8, val_name, "undefined");
}

comptime {
    // all false
    @compileLog(isUndefined(null));
    @compileLog(isUndefined(123));
    @compileLog(isUndefined(@as(u32, 123)));
    @compileLog(isUndefined(@as(?*anyopaque, @ptrFromInt(0x1000))));

    // all true
    @compileLog(isUndefined(@as(u32, undefined)));
    @compileLog(isUndefined(undefined));
    @compileLog(isUndefined(@as(u32, undefined) +% 1));
    @compileLog(isUndefined((&@as(u32, undefined)).*));
}

Does Zig support multiple returns, like Go and Lua?

Strictly speaking, no. Functions only have 0 or 1 returns.

However, Zig has multiple features that combine to most of what you'd expect from multiple returns:

  1. tuples, the return value of splitFirstWord, a struct definition without field names
  2. anonymous struct literals, used in the last line of splitFirstWord
  3. destructuring assignment, on the second line of the test
  4. indexing of tuples, in the second std.testing.expectEqualStrings call
const std = @import("std");

fn splitFirstWord(str: []const u8) struct { []const u8, []const u8 } { // (1)
    const first = std.mem.sliceTo(str, ' ');
    const rest = str[first.len..];
    return .{ first, rest }; // (2)
}

test "multiple return values" {
    const str = "hello world";
    const first, const rest = splitFirstWord(str); // (3)
    try std.testing.expectEqualStrings("hello", first);
    try std.testing.expectEqualStrings("hello", splitFirstWord(str)[0]); // (4)
    try std.testing.expectEqualStrings(" world", rest);
    try std.testing.expectEqual(str.ptr, first.ptr);
}

A gap in these features is that a function returning a single value cannot be augmented later to return a second value without modification of the function's callers. Likewise if a caller fully destructures a function's return, and then the function is changed to gain an additional return, this caller would have to be updated.

But is that a bad gap to have?

There's also Comptime/Named destructuring

How can you neatly check multiple options/errors at the same time?

c.f. #8351

test "multiple options" {
    const one: ?u32 = 1;
    const two: ?u32 = 2;

    multi: {
        if (one) |o1| if (two) |o2| {
            // ok
            _ = o1;
            _ = o2;
            break :multi;
        }; // required when nesting like this

        // one of those was null
        return error.NullFound;
    }
}

Does Zig have Closures?

No. And Andrew is quite opposed to helpers for closures. You can however close over state and use it yourself, manually:

const std = @import("std");

const LineInfo = struct {
    ax: f32,
    ay: f32,
    dx: f32,
    dy: f32,
    mag2: f32,

    pub fn fromLine(ax: f32, ay: f32, bx: f32, by: f32) LineInfo {
        const dx = bx - ax;
        const dy = by - ay;
        return .{
            .ax = ax,
            .ay = ay,
            .dx = dx,
            .dy = dy,
            .mag2 = dx * dx + dy * dy,
        };
    }

    pub fn nearest(info: LineInfo, px: f32, py: f32) struct { x: f32, y: f32 } {
        const a2pX = px - info.ax;
        const a2pY = py - info.ay;
        const a2pDota2b = a2pX * info.dx + a2pY * info.dy;
        const t = a2pDota2b / info.mag2;
        return .{
            .x = info.ax + info.dx * t,
            .y = info.ay + info.dy * t,
        };
    }
};

test LineInfo {
    std.debug.print("{any}\n", .{LineInfo.fromLine(0, 0, 5, 5).nearest(3, 3)});
    std.debug.print("{any}\n", .{LineInfo.fromLine(0, 0, 5, 5).nearest(3, 5)});
}

contrast Lua, using closures:

function nearestPointOnLine(ax, ay, bx, by)
  local dx = bx - ax
  local dy = by - ay
  local mag2 = dx*dx + dy*dy;

  return function(px, py)
    local a2pX = px - ax
    local a2pY = py - ay
    local a2pDota2b = a2pX*dx + a2pY*dy
    local t = a2pDota2b / mag2
    return {
      x = ax + dx*t,
      y = ay + dy*t,
    }
  end
end

local inspect = require 'inspect'
print(inspect(nearestPointOnLine(0, 0, 5, 5)(3, 3)))
print(inspect(nearestPointOnLine(0, 0, 5, 5)(3, 5)))