- How can I mutate a string literal?
- Can I detect if a value is
undefined? - Does Zig support multiple value returns, like Go and Lua?
- How can you neatly check multiple options/errors at the same time?
- 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:
- tuples, the return value of splitFirstWord, a struct definition without field names
- anonymous struct literals, used in the last line of splitFirstWord
- destructuring assignment, on the second line of the test
- 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)))