diff --git a/doc/langref.html.in b/doc/langref.html.in index 71f97b3f06..4ac9cf47e0 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -2025,7 +2025,8 @@ test "volatile" { conversions are not possible.
{#code_begin|test#} -const assert = @import("std").debug.assert; +const std = @import("std"); +const assert = std.debug.assert; test "pointer casting" { const bytes align(@alignOf(u32)) = [_]u8{ 0x12, 0x12, 0x12, 0x12 }; @@ -2034,7 +2035,7 @@ test "pointer casting" { // Even this example is contrived - there are better ways to do the above than // pointer casting. For example, using a slice narrowing cast: - const u32_value = @bytesToSlice(u32, bytes[0..])[0]; + const u32_value = std.mem.bytesAsSlice(u32, bytes[0..])[0]; assert(u32_value == 0x12121212); // And even another way, the most straightforward way to do it: @@ -2114,16 +2115,16 @@ test "function alignment" { {#link|safety check|Incorrect Pointer Alignment#}: {#code_begin|test_safety|incorrect alignment#} -const assert = @import("std").debug.assert; +const std = @import("std"); test "pointer alignment safety" { var array align(4) = [_]u32{ 0x11111111, 0x11111111 }; - const bytes = @sliceToBytes(array[0..]); - assert(foo(bytes) == 0x11111111); + const bytes = std.mem.sliceAsBytes(array[0..]); + std.debug.assert(foo(bytes) == 0x11111111); } fn foo(bytes: []u8) u32 { const slice4 = bytes[1..5]; - const int_slice = @bytesToSlice(u32, @alignCast(4, slice4)); + const int_slice = std.mem.bytesAsSlice(u32, @alignCast(4, slice4)); return int_slice[0]; } {#code_end#} @@ -2249,7 +2250,7 @@ test "slice widening" { // Zig supports slice widening and slice narrowing. Cast a slice of u8 // to a slice of anything else, and Zig will perform the length conversion. const array align(@alignOf(u32)) = [_]u8{ 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13 }; - const slice = @bytesToSlice(u32, array[0..]); + const slice = mem.bytesAsSlice(u32, array[0..]); assert(slice.len == 2); assert(slice[0] == 0x12121212); assert(slice[1] == 0x13131313); @@ -5186,7 +5187,6 @@ test "coercion of zero bit types" {{#syntax#}@bytesToSlice(comptime Element: type, bytes: []u8) []Element{#endsyntax#}
- - Converts a slice of bytes or array of bytes into a slice of {#syntax#}Element{#endsyntax#}. - The resulting slice has the same {#link|pointer|Pointers#} properties as the parameter. -
-- Attempting to convert a number of bytes with a length that does not evenly divide into a slice of - elements results in safety-protected {#link|Undefined Behavior#}. -
- {#header_close#} - {#header_open|@call#}{#syntax#}@call(options: std.builtin.CallOptions, function: var, args: var) var{#endsyntax#}
@@ -8101,14 +8088,6 @@ test "@setRuntimeSafety" { {#see_also|@bitSizeOf|@typeInfo#} {#header_close#} - {#header_open|@sliceToBytes#} -
{#syntax#}@sliceToBytes(value: var) []u8{#endsyntax#}
- - Converts a slice or array to a slice of {#syntax#}u8{#endsyntax#}. The resulting slice has the same - {#link|pointer|Pointers#} properties as the parameter. -
- {#header_close#} - {#header_open|@splat#}{#syntax#}@splat(comptime len: u32, scalar: var) @Vector(len, @TypeOf(scalar)){#endsyntax#}
@@ -8919,25 +8898,6 @@ pub fn main() void { var b: u32 = 3; var c = @divExact(a, b); std.debug.warn("value: {}\n", .{c}); -} - {#code_end#} - {#header_close#} - {#header_open|Slice Widen Remainder#} -
At compile-time:
- {#code_begin|test_err|unable to convert#} -comptime { - var bytes = [5]u8{ 1, 2, 3, 4, 5 }; - var slice = @bytesToSlice(u32, bytes[0..]); -} - {#code_end#} -At runtime:
- {#code_begin|exe_err#} -const std = @import("std"); - -pub fn main() void { - var bytes = [5]u8{ 1, 2, 3, 4, 5 }; - var slice = @bytesToSlice(u32, bytes[0..]); - std.debug.warn("value: {}\n", .{slice[0]}); } {#code_end#} {#header_close#} @@ -9119,14 +9079,15 @@ comptime { {#code_end#}At runtime:
{#code_begin|exe_err#} +const mem = @import("std").mem; pub fn main() !void { var array align(4) = [_]u32{ 0x11111111, 0x11111111 }; - const bytes = @sliceToBytes(array[0..]); + const bytes = mem.sliceAsBytes(array[0..]); if (foo(bytes) != 0x11111111) return error.Wrong; } fn foo(bytes: []u8) u32 { const slice4 = bytes[1..5]; - const int_slice = @bytesToSlice(u32, @alignCast(4, slice4)); + const int_slice = mem.bytesAsSlice(u32, @alignCast(4, slice4)); return int_slice[0]; } {#code_end#} diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 01e289988b..20ad567431 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -1488,28 +1488,34 @@ test "bytesToValue" { //TODO copy also is_volatile, etc. I tried to use @typeInfo, modify child type, use @Type, but ran into issues. fn BytesAsSliceReturnType(comptime T: type, comptime bytesType: type) type { - if (comptime !trait.isSlice(bytesType) or comptime meta.Child(bytesType) != u8) { - @compileError("expected []u8, passed " ++ @typeName(bytesType)); + if (!(trait.isSlice(bytesType) and meta.Child(bytesType) == u8) and !(trait.isPtrTo(.Array)(bytesType) and meta.Child(meta.Child(bytesType)) == u8)) { + @compileError("expected []u8 or *[_]u8, passed " ++ @typeName(bytesType)); } - const alignment = comptime meta.alignment(bytesType); + if (trait.isPtrTo(.Array)(bytesType) and @typeInfo(meta.Child(bytesType)).Array.len % @sizeOf(T) != 0) { + @compileError("number of bytes in " ++ @typeName(bytesType) ++ " is not divisible by size of " ++ @typeName(T)); + } - return if (comptime trait.isConstPtr(bytesType)) []align(alignment) const T else []align(alignment) T; + const alignment = meta.alignment(bytesType); + + return if (trait.isConstPtr(bytesType)) []align(alignment) const T else []align(alignment) T; } pub fn bytesAsSlice(comptime T: type, bytes: var) BytesAsSliceReturnType(T, @TypeOf(bytes)) { + const bytesSlice = if (comptime trait.isPtrTo(.Array)(@TypeOf(bytes))) bytes[0..] else bytes; + // let's not give an undefined pointer to @ptrCast // it may be equal to zero and fail a null check - if (bytes.len == 0) { + if (bytesSlice.len == 0) { return &[0]T{}; } - const bytesType = @TypeOf(bytes); + const bytesType = @TypeOf(bytesSlice); const alignment = comptime meta.alignment(bytesType); const castTarget = if (comptime trait.isConstPtr(bytesType)) [*]align(alignment) const T else [*]align(alignment) T; - return @ptrCast(castTarget, bytes.ptr)[0..@divExact(bytes.len, @sizeOf(T))]; + return @ptrCast(castTarget, bytesSlice.ptr)[0..@divExact(bytes.len, @sizeOf(T))]; } test "bytesAsSlice" { @@ -1520,30 +1526,59 @@ test "bytesAsSlice" { testing.expect(bigToNative(u16, slice[1]) == 0xBEEF); } +test "bytesAsSlice keeps pointer alignment" { + var bytes = [_]u8{ 0x01, 0x02, 0x03, 0x04 }; + const numbers = bytesAsSlice(u32, bytes[0..]); + comptime testing.expect(@TypeOf(numbers) == []align(@alignOf(@TypeOf(bytes))) u32); +} + +test "bytesAsSlice on a packed struct" { + const F = packed struct { + a: u8, + }; + + var b = [1]u8{9}; + var f = bytesAsSlice(F, &b); + testing.expect(f[0].a == 9); +} + +test "bytesAsSlice with specified alignment" { + var bytes align(4) = [_]u8{ + 0x33, + 0x33, + 0x33, + 0x33, + }; + const slice: []u32 = std.mem.bytesAsSlice(u32, bytes[0..]); + testing.expect(slice[0] == 0x33333333); +} + //TODO copy also is_volatile, etc. I tried to use @typeInfo, modify child type, use @Type, but ran into issues. fn SliceAsBytesReturnType(comptime sliceType: type) type { - if (comptime !trait.isSlice(sliceType)) { - @compileError("expected []T, passed " ++ @typeName(sliceType)); + if (!trait.isSlice(sliceType) and !trait.isPtrTo(.Array)(sliceType)) { + @compileError("expected []T or *[_]T, passed " ++ @typeName(sliceType)); } - const alignment = comptime meta.alignment(sliceType); + const alignment = meta.alignment(sliceType); - return if (comptime trait.isConstPtr(sliceType)) []align(alignment) const u8 else []align(alignment) u8; + return if (trait.isConstPtr(sliceType)) []align(alignment) const u8 else []align(alignment) u8; } pub fn sliceAsBytes(slice: var) SliceAsBytesReturnType(@TypeOf(slice)) { + const actualSlice = if (comptime trait.isPtrTo(.Array)(@TypeOf(slice))) slice[0..] else slice; + // let's not give an undefined pointer to @ptrCast // it may be equal to zero and fail a null check - if (slice.len == 0) { + if (actualSlice.len == 0) { return &[0]u8{}; } - const sliceType = @TypeOf(slice); + const sliceType = @TypeOf(actualSlice); const alignment = comptime meta.alignment(sliceType); const castTarget = if (comptime trait.isConstPtr(sliceType)) [*]align(alignment) const u8 else [*]align(alignment) u8; - return @ptrCast(castTarget, slice.ptr)[0 .. slice.len * @sizeOf(comptime meta.Child(sliceType))]; + return @ptrCast(castTarget, actualSlice.ptr)[0 .. actualSlice.len * @sizeOf(comptime meta.Child(sliceType))]; } test "sliceAsBytes" { @@ -1556,6 +1591,57 @@ test "sliceAsBytes" { })); } +test "sliceAsBytes packed struct at runtime and comptime" { + const Foo = packed struct { + a: u4, + b: u4, + }; + const S = struct { + fn doTheTest() void { + var foo: Foo = undefined; + var slice = sliceAsBytes(@as(*[1]Foo, &foo)[0..1]); + slice[0] = 0x13; + switch (builtin.endian) { + .Big => { + testing.expect(foo.a == 0x1); + testing.expect(foo.b == 0x3); + }, + .Little => { + testing.expect(foo.a == 0x3); + testing.expect(foo.b == 0x1); + }, + } + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "sliceAsBytes and bytesAsSlice back" { + testing.expect(@sizeOf(i32) == 4); + + var big_thing_array = [_]i32{ 1, 2, 3, 4 }; + const big_thing_slice: []i32 = big_thing_array[0..]; + + const bytes = sliceAsBytes(big_thing_slice); + testing.expect(bytes.len == 4 * 4); + + bytes[4] = 0; + bytes[5] = 0; + bytes[6] = 0; + bytes[7] = 0; + testing.expect(big_thing_slice[1] == 0); + + const big_thing_again = bytesAsSlice(i32, bytes); + testing.expect(big_thing_again[2] == 3); + + big_thing_again[2] = -1; + testing.expect(bytes[8] == math.maxInt(u8)); + testing.expect(bytes[9] == math.maxInt(u8)); + testing.expect(bytes[10] == math.maxInt(u8)); + testing.expect(bytes[11] == math.maxInt(u8)); +} + fn SubArrayPtrReturnType(comptime T: type, comptime length: usize) type { if (trait.isConstPtr(T)) return *const [length]meta.Child(meta.Child(T)); diff --git a/lib/std/meta/trait.zig b/lib/std/meta/trait.zig index b1fd233ff2..b17a4a2a93 100644 --- a/lib/std/meta/trait.zig +++ b/lib/std/meta/trait.zig @@ -135,6 +135,22 @@ test "std.meta.trait.isPtrTo" { testing.expect(!isPtrTo(.Struct)(**struct {})); } +pub fn isSliceOf(comptime id: builtin.TypeId) TraitFn { + const Closure = struct { + pub fn trait(comptime T: type) bool { + if (!comptime isSlice(T)) return false; + return id == @typeId(meta.Child(T)); + } + }; + return Closure.trait; +} + +test "std.meta.trait.isSliceOf" { + testing.expect(!isSliceOf(.Struct)(struct {})); + testing.expect(isSliceOf(.Struct)([]struct {})); + testing.expect(!isSliceOf(.Struct)([][]struct {})); +} + ///////////Strait trait Fns //@TODO: diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 1b32786e40..6213e8435f 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -4747,16 +4747,6 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { "tmp.zig:1:15: error: comptime parameter not allowed in function with calling convention 'C'", }); - cases.add("convert fixed size array to slice with invalid size", - \\export fn f() void { - \\ var array: [5]u8 = undefined; - \\ var foo = @bytesToSlice(u32, &array)[0]; - \\} - , &[_][]const u8{ - "tmp.zig:3:15: error: unable to convert [5]u8 to []align(1) u32: size mismatch", - "tmp.zig:3:29: note: u32 has size 4; remaining bytes: 1", - }); - cases.add("non-pure function returns type", \\var a: u32 = 0; \\pub fn List(comptime T: type) type { diff --git a/test/runtime_safety.zig b/test/runtime_safety.zig index 2217a7f2df..e4ea7f52db 100644 --- a/test/runtime_safety.zig +++ b/test/runtime_safety.zig @@ -553,15 +553,16 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { ); cases.addRuntimeSafety("cast []u8 to bigger slice of wrong size", - \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { - \\ @import("std").os.exit(126); + \\const std = @import("std"); + \\pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { + \\ std.os.exit(126); \\} \\pub fn main() !void { \\ const x = widenSlice(&[_]u8{1, 2, 3, 4, 5}); \\ if (x.len == 0) return error.Whatever; \\} \\fn widenSlice(slice: []align(1) const u8) []align(1) const i32 { - \\ return @bytesToSlice(i32, slice); + \\ return std.mem.bytesAsSlice(i32, slice); \\} ); @@ -656,17 +657,18 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { ); cases.addRuntimeSafety("@alignCast misaligned", - \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { - \\ @import("std").os.exit(126); + \\const std = @import("std"); + \\pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { + \\ std.os.exit(126); \\} \\pub fn main() !void { \\ var array align(4) = [_]u32{0x11111111, 0x11111111}; - \\ const bytes = @sliceToBytes(array[0..]); + \\ const bytes = std.mem.sliceAsBytes(array[0..]); \\ if (foo(bytes) != 0x11111111) return error.Wrong; \\} \\fn foo(bytes: []u8) u32 { \\ const slice4 = bytes[1..5]; - \\ const int_slice = @bytesToSlice(u32, @alignCast(4, slice4)); + \\ const int_slice = std.mem.bytesAsSlice(u32, @alignCast(4, slice4)); \\ return int_slice[0]; \\} ); diff --git a/test/stage1/behavior.zig b/test/stage1/behavior.zig index ea8720d98c..6e0d4c5e22 100644 --- a/test/stage1/behavior.zig +++ b/test/stage1/behavior.zig @@ -92,7 +92,6 @@ comptime { _ = @import("behavior/shuffle.zig"); _ = @import("behavior/sizeof_and_typeof.zig"); _ = @import("behavior/slice.zig"); - _ = @import("behavior/slicetobytes.zig"); _ = @import("behavior/struct.zig"); _ = @import("behavior/struct_contains_null_ptr_itself.zig"); _ = @import("behavior/struct_contains_slice_of_itself.zig"); diff --git a/test/stage1/behavior/align.zig b/test/stage1/behavior/align.zig index c83d2379b4..5a5138d567 100644 --- a/test/stage1/behavior/align.zig +++ b/test/stage1/behavior/align.zig @@ -81,20 +81,6 @@ fn testBytesAlign(b: u8) void { expect(ptr.* == 0x33333333); } -test "specifying alignment allows slice cast" { - testBytesAlignSlice(0x33); -} -fn testBytesAlignSlice(b: u8) void { - var bytes align(4) = [_]u8{ - b, - b, - b, - b, - }; - const slice: []u32 = @bytesToSlice(u32, bytes[0..]); - expect(slice[0] == 0x33333333); -} - test "@alignCast pointers" { var x: u32 align(4) = 1; expectsOnly1(&x); diff --git a/test/stage1/behavior/async_fn.zig b/test/stage1/behavior/async_fn.zig index b04d4e2b5d..6583adec66 100644 --- a/test/stage1/behavior/async_fn.zig +++ b/test/stage1/behavior/async_fn.zig @@ -334,7 +334,7 @@ test "async fn with inferred error set" { var frame: [1]@Frame(middle) = undefined; var fn_ptr = middle; var result: @TypeOf(fn_ptr).ReturnType.ErrorSet!void = undefined; - _ = @asyncCall(@sliceToBytes(frame[0..]), &result, fn_ptr); + _ = @asyncCall(std.mem.sliceAsBytes(frame[0..]), &result, fn_ptr); resume global_frame; std.testing.expectError(error.Fail, result); } @@ -954,7 +954,7 @@ test "@asyncCall with comptime-known function, but not awaited directly" { fn doTheTest() void { var frame: [1]@Frame(middle) = undefined; var result: @TypeOf(middle).ReturnType.ErrorSet!void = undefined; - _ = @asyncCall(@sliceToBytes(frame[0..]), &result, middle); + _ = @asyncCall(std.mem.sliceAsBytes(frame[0..]), &result, middle); resume global_frame; std.testing.expectError(error.Fail, result); } diff --git a/test/stage1/behavior/bugs/1851.zig b/test/stage1/behavior/bugs/1851.zig index f3e7393b96..d6cf17651d 100644 --- a/test/stage1/behavior/bugs/1851.zig +++ b/test/stage1/behavior/bugs/1851.zig @@ -13,7 +13,7 @@ test "allocation and looping over 3-byte integer" { x[0] = 0xFFFFFF; x[1] = 0xFFFFFF; - const bytes = @sliceToBytes(x); + const bytes = std.mem.sliceAsBytes(x); expect(@TypeOf(bytes) == []align(4) u8); expect(bytes.len == 8); diff --git a/test/stage1/behavior/cast.zig b/test/stage1/behavior/cast.zig index f393bfebb5..76d3b784ec 100644 --- a/test/stage1/behavior/cast.zig +++ b/test/stage1/behavior/cast.zig @@ -304,20 +304,6 @@ fn cast128Float(x: u128) f128 { return @bitCast(f128, x); } -test "const slice widen cast" { - const bytes align(4) = [_]u8{ - 0x12, - 0x12, - 0x12, - 0x12, - }; - - const u32_value = @bytesToSlice(u32, bytes[0..])[0]; - expect(u32_value == 0x12121212); - - expect(@bitCast(u32, bytes) == 0x12121212); -} - test "single-item pointer of array to slice and to unknown length pointer" { testCastPtrOfArrayToSliceAndPtr(); comptime testCastPtrOfArrayToSliceAndPtr(); @@ -392,12 +378,6 @@ test "comptime_int @intToFloat" { } } -test "@bytesToSlice keeps pointer alignment" { - var bytes = [_]u8{ 0x01, 0x02, 0x03, 0x04 }; - const numbers = @bytesToSlice(u32, bytes[0..]); - comptime expect(@TypeOf(numbers) == []align(@alignOf(@TypeOf(bytes))) u32); -} - test "@intCast i32 to u7" { var x: u128 = maxInt(u128); var y: i32 = 120; diff --git a/test/stage1/behavior/eval.zig b/test/stage1/behavior/eval.zig index ed81fae791..099434a869 100644 --- a/test/stage1/behavior/eval.zig +++ b/test/stage1/behavior/eval.zig @@ -711,16 +711,6 @@ test "bit shift a u1" { expect(y == 1); } -test "@bytesToslice on a packed struct" { - const F = packed struct { - a: u8, - }; - - var b = [1]u8{9}; - var f = @bytesToSlice(F, &b); - expect(f[0].a == 9); -} - test "comptime pointer cast array and then slice" { const array = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8 }; diff --git a/test/stage1/behavior/misc.zig b/test/stage1/behavior/misc.zig index c77b49f03a..a3a008c1d2 100644 --- a/test/stage1/behavior/misc.zig +++ b/test/stage1/behavior/misc.zig @@ -3,7 +3,6 @@ const expect = std.testing.expect; const expectEqualSlices = std.testing.expectEqualSlices; const mem = std.mem; const builtin = @import("builtin"); -const maxInt = std.math.maxInt; // normal comment @@ -377,26 +376,6 @@ test "string concatenation" { expect(b[len] == 0); } -test "cast slice to u8 slice" { - expect(@sizeOf(i32) == 4); - var big_thing_array = [_]i32{ 1, 2, 3, 4 }; - const big_thing_slice: []i32 = big_thing_array[0..]; - const bytes = @sliceToBytes(big_thing_slice); - expect(bytes.len == 4 * 4); - bytes[4] = 0; - bytes[5] = 0; - bytes[6] = 0; - bytes[7] = 0; - expect(big_thing_slice[1] == 0); - const big_thing_again = @bytesToSlice(i32, bytes); - expect(big_thing_again[2] == 3); - big_thing_again[2] = -1; - expect(bytes[8] == maxInt(u8)); - expect(bytes[9] == maxInt(u8)); - expect(bytes[10] == maxInt(u8)); - expect(bytes[11] == maxInt(u8)); -} - test "pointer to void return type" { testPointerToVoidReturnType() catch unreachable; } diff --git a/test/stage1/behavior/slicetobytes.zig b/test/stage1/behavior/slicetobytes.zig deleted file mode 100644 index c0eb4a8f9b..0000000000 --- a/test/stage1/behavior/slicetobytes.zig +++ /dev/null @@ -1,29 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const expect = std.testing.expect; - -test "@sliceToBytes packed struct at runtime and comptime" { - const Foo = packed struct { - a: u4, - b: u4, - }; - const S = struct { - fn doTheTest() void { - var foo: Foo = undefined; - var slice = @sliceToBytes(@as(*[1]Foo, &foo)[0..1]); - slice[0] = 0x13; - switch (builtin.endian) { - builtin.Endian.Big => { - expect(foo.a == 0x1); - expect(foo.b == 0x3); - }, - builtin.Endian.Little => { - expect(foo.a == 0x3); - expect(foo.b == 0x1); - }, - } - } - }; - S.doTheTest(); - comptime S.doTheTest(); -} diff --git a/test/stage1/behavior/struct.zig b/test/stage1/behavior/struct.zig index 8e5ab29db3..ecec7fe5d6 100644 --- a/test/stage1/behavior/struct.zig +++ b/test/stage1/behavior/struct.zig @@ -315,7 +315,7 @@ test "packed array 24bits" { var bytes = [_]u8{0} ** (@sizeOf(FooArray24Bits) + 1); bytes[bytes.len - 1] = 0xaa; - const ptr = &@bytesToSlice(FooArray24Bits, bytes[0 .. bytes.len - 1])[0]; + const ptr = &std.mem.bytesAsSlice(FooArray24Bits, bytes[0 .. bytes.len - 1])[0]; expect(ptr.a == 0); expect(ptr.b[0].field == 0); expect(ptr.b[1].field == 0); @@ -364,7 +364,7 @@ test "aligned array of packed struct" { } var bytes = [_]u8{0xbb} ** @sizeOf(FooArrayOfAligned); - const ptr = &@bytesToSlice(FooArrayOfAligned, bytes[0..bytes.len])[0]; + const ptr = &std.mem.bytesAsSlice(FooArrayOfAligned, bytes[0..])[0]; expect(ptr.a[0].a == 0xbb); expect(ptr.a[0].b == 0xbb);