Edit: Here’s an even more simplified example:
const std = @import("std");
fn tuplize(
// foo
T: type,
U: type,
V: type,
in: struct { T, U, V },
) struct { struct { T, U }, V } {
return .{ .{ in[0], in[1] }, in[2] };
}
// segfault :(
test "tuplize" {
const in = .{ 'a', 'b', 'c' };
const out = tuplize(u8, u8, u8, in);
try std.testing.expectEqual(.{ .{ 'a', 'b' }, 'c' }, out);
}
Hi, I am a moron!
I’m doing Advent of Code 2024, and I’m trying to build a functional parser. However, I’m getting a weird segfault when I return a tuple inside my output, but not when I return a flat struct. Here’s a minimal example:
const std = @import("std");
// Works as expected
fn okay() fn (x: u8) struct { u8, u8, u8 } {
return struct {
pub fn invoke(x: u8) struct { u8, u8, u8 } {
return .{ x * 2, x ^ 2, x };
}
}.invoke;
}
// Segfaults
fn broken() fn (x: u8) struct { struct { u8, u8 }, u8 } {
return struct {
pub fn invoke(x: u8) struct { struct { u8, u8 }, u8 } {
return .{ .{ x * 2, x ^ 2 }, x };
}
}.invoke;
}
test "okay" {
const x = okay()(3);
std.debug.print("{}", .{x});
}
test "broken" {
const x = broken()(3);
std.debug.print("{}", .{x});
}
For some insight in to this madness, here’s the actual function I’m trying to write.
const std = @import("std");
// A Y-Combinator or functional parser.
// Accepts an array of bytes and returns a value (T),
// and the unparsed bytes.
fn Parser(comptime T: type) type {
return fn ([]u8) struct { T, []u8 };
}
// Given three parsers, return a metaparser that returns
// the results of the first and third parsers, discarding
// the results from the second parser.
//
// This one doesn't work
fn delimited(
//
comptime T: type,
comptime U: type,
comptime first: Parser(T),
comptime second: Parser(void),
comptime third: Parser(U),
) Parser(struct { T, U }) {
return struct {
pub fn invoke(bytes: []u8) struct { struct {T, U}, []u8 } {
const first_result = first(bytes);
var rest = first_result[1];
const t = first_result[0];
rest = second(rest)[1];
const third_result = third(rest);
const u = third_result[0];
rest = third_result[1];
return .{ .{ t, u }, rest };
}
}.invoke;
}
// Same as above, only this one doesn't segfault on me.
fn delimited_no_segfault(
//
comptime T: type,
comptime U: type,
comptime first: Parser(T),
comptime second: Parser(void),
comptime third: Parser(U),
) fn (input: []u8) struct { T, U, []u8 } {
return struct {
pub fn invoke(bytes: []u8) struct { T, U, []u8 } {
const first_result = first(bytes);
var rest = first_result[1];
const t = first_result[0];
rest = second(rest)[1];
const third_result = third(rest);
const u = third_result[0];
rest = third_result[1];
return .{ t, u, rest };
}
}.invoke;
}
I realize this isn’t terribly idiomatic, but I couldn’t (quickly) find a good Y-Combinator alternative. Is there something wrong with Zig, or am I just bending this language too far out of shape?