# Object pools with structs and arrays of varying sizes by using \`std.mem.bytesAsValue/Slice\`

**URL:** <https://ziggit.dev/t/object-pools-with-structs-and-arrays-of-varying-sizes-by-using-std-mem-bytesasvalue-slice/17648>\
**Category:** Explain\
**Tags:** memory-management, standard-library\
**Created:** [September 18, 2026, 9:55pm UTC](https://ziggit.dev/t/object-pools-with-structs-and-arrays-of-varying-sizes-by-using-std-mem-bytesasvalue-slice/17648 "2026-09-18T21:55:37Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![chearon](https://ziggit.dev/user_avatar/ziggit.dev/chearon/32/4947_2.png) [@chearon](https://ziggit.dev/u/chearon)\
**Post date:** [September 18, 2026, 9:55pm UTC](https://ziggit.dev/t/object-pools-with-structs-and-arrays-of-varying-sizes-by-using-std-mem-bytesasvalue-slice/17648/1 "2026-09-18T21:55:37Z")

</div>

I’ve been experimenting with object pools that allow you to mint arbitrary types, very much inspired by Zig’s `InternPool`. Like `InternPool`, the backing buffer is `[]u32` and the index is `u32`.

The first API I added was `addUndefined`, which takes a `count` argument:

```zig
pub const PackedData = struct {
    entries: []u32,
    capacity: u32,
    index: u32,

    pub fn addUndefined(
          data: *PackedData,
          allocator: std.mem.Allocator,
          T: type,
          count: u32
    ) !u32 {
        // Ensure space for ⌈@sizeOf(T) * count / @sizeOf(u32)⌉
        // Increment index by that much
        // Return original index
    }
};

```

That lets me mint variable-length data of varying integer size that can be safely reified into slices, which I fill in later. The way I’m doing that is by doing `u32[]` → `std.mem.sliceAsBytes` → `std.mem.bytesAsSlice` → `u16[]` or `u8[]`. I tested that this is safe:

```zig
test "bytes as slice larger than the bytes slice" {
    var array: [2]u32 = undefined;
    const slice: []u32 = &array;
    _ = std.mem.bytesAsSlice(u128, std.mem.sliceAsBytes(slice)); // panics at 8 / 16
}

```

Next I wanted to mint structs. It looks like `InternPool` splits up struct fields and puts each one in its own `u32` slot. I tried this, but it didn’t work out for me. And since `entries` already has a `len`, and `@sizeOf(T)` is known, shouldn’t `std.mem.bytesAsValue` be able to safety-check just like `std.mem.bytesAsSlice` does? It doesn’t currently, because `bytesAsValue` is implemented merely as a `@ptrCast`:

```zig
const Four = packed struct {a: u16, b: u16, c: u16, d: u16, ohno: u16};

test "bytes as struct larger than the bytes slice" {
    var array: [2]u32 = undefined;
    const slice: []u32 = &array;
    var four = std.mem.bytesAsValue(Four, std.mem.sliceAsBytes(slice)); // no error!
    four.ohno = 1; // traps, but is it safe to get that far?
}

```

So my questions are:

1. Why doesn’t `mem.bytesAsValue` add a check like `if (@sizeOf(T) > bytes.len) unreachable;`? Or is that not all that’s needed?
2. Are there better solutions to storing variable sized types together? Has anyone had to implement anything similar?

---

<div class="post-metadata">

**Author:** ![alanza](https://ziggit.dev/user_avatar/ziggit.dev/alanza/32/6006_2.png) [@alanza](https://ziggit.dev/u/alanza)\
**Post date:** [September 18, 2026, 11:26pm UTC](https://ziggit.dev/t/object-pools-with-structs-and-arrays-of-varying-sizes-by-using-std-mem-bytesasvalue-slice/17648/2 "2026-09-18T23:26:05Z")

</div>

1. You also should care at least a little about alignment. pointers of type `*T` have (implicitly) some alignment data about them which Zig enforces during non-casting operations. The cast in `mem.bytesAsValue` does _not_ contain an `@alignCast`, so you should a compile error if you get this wrong.  
Also, yes, that’s not all that’s needed. Many types in Zig are not “total” in the sense that there are bit patterns of `@sizeOf(T)` bytes which do not represent a valid object of type `T`. Exhaustive enums which don’t cover the range of their backing integer are a good example of this, as are tagged unions.
2. It sounds like if you squint for a second you’ll realize that you’re on your way to implementing `std.mem.Allocator`, which is designed precisely to dole out memory for variably sized types.

---

<div class="post-metadata">

**Author:** ![chearon](https://ziggit.dev/user_avatar/ziggit.dev/chearon/32/4947_2.png) [@chearon](https://ziggit.dev/u/chearon)\
**Post date:** [September 19, 2026, 5:49pm UTC](https://ziggit.dev/t/object-pools-with-structs-and-arrays-of-varying-sizes-by-using-std-mem-bytesasvalue-slice/17648/3 "2026-09-19T17:49:51Z")

</div>

Thanks for the response! Curiously, `std.mem.bytesAsSlice` doesn’t check alignment:

```zig
test "does bytesAsSlice fail on misalignment?" {
    var array: [4]u8 = undefined;
    const slice: []u8 = &array;
    _ = std.mem.bytesAsSlice(u16, std.mem.sliceAsBytes(slice[1..3])); // no!
}

```

So `bytesAsSlice` checks for fit, but not alignment, and `bytesAsValue` is the opposite. Ideally they both check both, no?

> Many types in Zig are not “total”

I see. I tried switching an exhaustive enum where the backing integer isn’t covered, and that is safety-checked, at least.

I did start to think “is this just Allocator with extra steps?”. But if I use an allocator, I’d need 8 bytes to reference an object instead of 4, and I don’t get temporal memory safety. These objects all live together, so I don’t want to have to free them individually. I could use an arena allocator, but it’d be nice if they took up contiguous memory, since they pretty much all get accessed and in logical order. I can’t think of how to make it perfect with an Allocator.

Maybe I’ll implement my own `bytesAsValue` with length check if it doesn’t sound crazy.

---

<div class="post-metadata">

**Author:** ![alanza](https://ziggit.dev/user_avatar/ziggit.dev/alanza/32/6006_2.png) [@alanza](https://ziggit.dev/u/alanza)\
**Post date:** [September 19, 2026, 6:50pm UTC](https://ziggit.dev/t/object-pools-with-structs-and-arrays-of-varying-sizes-by-using-std-mem-bytesasvalue-slice/17648/4 "2026-09-19T18:50:00Z")

</div>

I’m not sure that it is failing. to properly test for that you should actually give the return value type `*u16` before discarding it. Probably the function is doing a sensible if slightly perverse thing of returning a pointer of type `*align(1) u16`
