kj4tmp
October 5, 2025, 2:12am
1
I’m trying to reinterpret unaligned memory to avoid allocation:
const std = @import("std");
const builtin = @import("builtin");
fn deserializeInPlace(buf: []u8) ![]align(1) const u16 {
if (buf.len % 2 != 0) return error.WrongLen;
const slice: []align(1) u16 = std.mem.bytesAsSlice(u16, buf);
switch (builtin.cpu.arch.endian()) {
.little => {},
.big => {
// NOTE: this doesnt work due to alignment
// std.mem.byteSwapAllElements(u16, slice)
// test5.zig:9:50: note: pointer alignment '1' cannot cast into pointer alignment '2'
// note: parameter type declared here
// pub fn byteSwapAllElements(comptime Elem: type, slice: []Elem) void {
// ^~~~~~
for (slice) |*elem| {
elem.* = @byteSwap(elem.*);
}
},
}
return slice;
}
test {
var bytes: [4]u8 = .{ 0x01, 0x02, 0x03, 0x04 };
const nums = try deserializeInPlace(&bytes);
const expected: []const u16 align(1) = &.{ 0x0201, 0x0403 };
try std.testing.expectEqualSlices(u16, expected, nums);
}
$ zig test test5.zig
anyzig: zig version '0.15.1' pulled from '/home/jeff/repos/untracked/build.zig.zon'
anyzig: appdata '/home/jeff/.local/share/anyzig'
anyzig: zig '0.15.1' already exists at '/home/jeff/.cache/zig/p/N-V-__8AAN5NhBR0oTsvnwjPdeNiiDLtEsfXRHd1fv-R3TOv'
test5.zig:28:54: error: expected type '[]const u16', found '[]align(1) const u16'
try std.testing.expectEqualSlices(u16, expected, nums);
^~~~
test5.zig:28:54: note: pointer alignment '1' cannot cast into pointer alignment '2'
/home/jeff/.cache/zig/p/N-V-__8AAN5NhBR0oTsvnwjPdeNiiDLtEsfXRHd1fv-R3TOv/lib/std/testing.zig:363:73: note: parameter type declared here
pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const T) !void {
But I am having trouble even testing it, it doesn’t seem like unaligned slices are well supported by the standard library. Am I doing something wrong, is there a better way to do this that I am not seeing?
You may be interested in std.mem.readInt which can be used to read unaligned integers from a byte array, with a specified endian-ness.
const std = @import("std");
const builtin = @import("builtin");
const testing = @import("testing");
pub fn main() !void {
var bytes: [4]u8 = .{ 0x01, 0x02, 0x03, 0x04 };
const nums = try deserializeInPlace(&bytes);
std.debug.print("{any}", .{nums});
}
fn deserializeInPlace(buf: []u8) ![]const u16 {
if (buf.len % 2 != 0) return error.WrongLen;
const slice: []u16 = std.mem.bytesAsSlice(u16, @as([]u16, @ptrCast(@alignCast(buf))));
switch (builtin.cpu.arch.endian()) {
.little => {},
.big => {
// NOTE: this doesnt work due to alignment
// std.mem.byteSwapAllElements(u16, slice)
// test5.zig:9:50: note: pointer alignment '1' cannot cast into pointer alignment '2'
// note: parameter type declared here
// pub fn byteSwapAllElements(comptime Elem: type, slice: []Elem) void {
// ^~~~~~
for (slice) |*elem| {
elem.* = @byteSwap(elem.*);
}
},
}
return slice;
}
test {
const expected: []const u16 = &.{ 0x0201, 0x0403 };
var bytes: [4]u8 = .{ 0x01, 0x02, 0x03, 0x04 };
const nums = try deserializeInPlace(&bytes);
try std.testing.expectEqualSlices(u16, expected, nums);
}
Documents/test/testing ❯ zig run ./src/main.zig zig 00:07
{ 513 }%
Documents/test/testing ❯ zig test ./src/main.zig zig 00:07
slices differ. first difference occurs at index 1 (0x1)
============ expected this output: ============= len: 2 (0x2)
[0]: 513
[1]: 1027
============= instead found this: ============== len: 1 (0x1)
[0]: 513
================================================
1/1 main.test_0...FAIL (TestExpectedEqual)
/home/james/zig/0.15.1/files/lib/std/testing.zig:376:5: 0x102ef37 in expectEqualSlices__anon_1576 (std.zig)
return error.TestExpectedEqual;
^
/home/james/Documents/test/testing/src/main.zig:35:5: 0x102f131 in test_0 (main.zig)
try std.testing.expectEqualSlices(u16, expected, nums);
^
0 passed; 0 skipped; 1 failed.
error: the following test command failed with exit code 1:
.zig-cache/o/2d038284fd934167d906265621c1d8b8/test --seed=0xf19e1a55
Here is the answer to pass the test case:
const std = @import("std");
const builtin = @import("builtin");
const testing = @import("testing");
pub fn main() !void {
var bytes: [4]u8 = .{ 0x01, 0x02, 0x03, 0x04 };
const nums = try deserializeInPlace(&bytes);
std.debug.print("{any}", .{nums});
}
fn deserializeInPlace(buf: []u8) ![]u16 {
if (buf.len % 2 != 0) return error.WrongLen;
std.debug.print("{any}", .{buf});
const slice = std.mem.bytesAsSlice(u16, buf);
switch (builtin.cpu.arch.endian()) {
.little => {},
.big => {
// NOTE: this doesnt work due to alignment
// std.mem.byteSwapAllElements(u16, slice)
// test5.zig:9:50: note: pointer alignment '1' cannot cast into pointer alignment '2'
// note: parameter type declared here
// pub fn byteSwapAllElements(comptime Elem: type, slice: []Elem) void {
// ^~~~~~
for (slice) |*elem| {
elem.* = @byteSwap(elem.*);
}
},
}
return @alignCast(slice);
}
test {
const expected: []const u16 = &.{ 0x0201, 0x0403 };
var bytes: [4]u8 = .{ 0x01, 0x02, 0x03, 0x04 };
const nums = try deserializeInPlace(&bytes);
try std.testing.expectEqualSlices(u16, expected, nums);
}
kj4tmp
October 5, 2025, 5:24am
5
Welcome to ziggit!
The implementation needs to read from align(1) memory. Your implementation assumes that the input buffer is align(2), you can illustrate the failure with this:
test {
var bytes: [5]u8 = .{ 0x00, 0x01, 0x02, 0x03, 0x04 };
const nums = try deserializeInPlace(bytes[1..]);
const expected: []const u16 align(1) = &.{ 0x0201, 0x0403 };
try std.testing.expectEqual(nums[0], expected[0]);
try std.testing.expectEqual(nums[1], expected[1]);
}
{ 1, 2, 3, 4 }thread 1439595 panic: incorrect alignment
/home/jeff/repos/untracked/test5.zig:22:12: 0x102c1a6 in deserializeInPlace (test5.zig)
return @alignCast(slice);
^
/home/jeff/repos/untracked/test5.zig:27:40: 0x102c3e6 in test_0 (test5.zig)
const nums = try deserializeInPlace(bytes[1..]);
You have two options, either require the input data to be overaligned or have the result be underaligned. Having both have their natural alignments will require copying to a new buffer. Both have their pros and cons so whichever is better is up to you.
try std.testing.expectEqualSlices(u16, expected, nums);
can be rewritten as
for (expected, nums) |e, n| {
try std.testing.expectEqual(e, n);
}
2 Likes