From c9533046ca37a053f487f4002a999a29a96de517 Mon Sep 17 00:00:00 2001 From: Robbie Lyman Date: Sun, 12 Jul 2026 00:53:34 -0400 Subject: [PATCH 1/7] fix: Allocator contract should allow *[len]T in more places This commit continues work begun in #35222. Although after #35222 is now legal to call `Allocator.free` on memory of type *[len]T, code which does still does not compile. Additionally, similarly shaped footguns remain; this commit addresses those. --- lib/std/mem/Allocator.zig | 48 +++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/lib/std/mem/Allocator.zig b/lib/std/mem/Allocator.zig index 486c69966e0e3d9bccf1b99e92d404cc36363283..e442427169f56898da818f11e3587ebe4d348dc2 100644 --- a/lib/std/mem/Allocator.zig +++ b/lib/std/mem/Allocator.zig @@ -305,6 +305,25 @@ pub fn allocBytesAligned( return @alignCast(byte_ptr); } +fn SliceType(comptime Pointer: type) type { + const info = @typeInfo(Pointer).pointer; + switch (info.size) { + .slice => return Pointer, + .one => { + const child_info = @typeInfo(info.child); + comptime assert(child_info == .array); + const sentinel_ptr: ?*const child_info.array.child = @ptrCast(@alignCast(child_info.array.sentinel_ptr)); + return @Pointer( + .slice, + info.attrs, + child_info.array.child, + if (sentinel_ptr) |ptr| ptr.* else null, + ); + }, + else => unreachable, + } +} + /// Request to modify the size of an allocation. /// /// It is guaranteed to not move the pointer, however the allocator @@ -316,6 +335,10 @@ pub fn allocBytesAligned( /// `new_len` may be zero, in which case the allocation is freed. pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool { const slice_info = @typeInfo(@TypeOf(allocation)).pointer; + if (slice_info.size != .slice) { + const slice: SliceType(@TypeOf(allocation)) = allocation; // coerce *[len]T to []T + return resize(self, slice, new_len); + } comptime assert(slice_info.size == .slice); const T = slice_info.child; if (new_len == 0) { @@ -354,8 +377,12 @@ pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool { /// `new_len` may be zero, in which case the allocation is freed. /// /// If the allocation's elements' type is zero bytes sized, `allocation.len` is set to `new_len`. -pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?@TypeOf(allocation) { +pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?SliceType(@TypeOf(allocation)) { const slice_info = @typeInfo(@TypeOf(allocation)).pointer; + if (slice_info.size != .slice) { + const slice: SliceType(@TypeOf(allocation)) = allocation; // coerce *[len]T to []T + return remap(self, slice, new_len); + } comptime assert(slice_info.size == .slice); const T = slice_info.child; @@ -399,7 +426,7 @@ pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?@TypeOf(allo /// do the realloc more efficiently than the caller /// * `resize` which returns `false` when the `Allocator` implementation cannot /// change the size without relocating the allocation. -pub fn realloc(self: Allocator, old_mem: anytype, new_n: usize) Error!@TypeOf(old_mem) { +pub fn realloc(self: Allocator, old_mem: anytype, new_n: usize) Error!SliceType(@TypeOf(old_mem)) { return self.reallocAdvanced(old_mem, new_n, @returnAddress()); } @@ -408,8 +435,12 @@ pub fn reallocAdvanced( old_mem: anytype, new_n: usize, return_address: usize, -) Error!@TypeOf(old_mem) { +) Error!SliceType(@TypeOf(old_mem)) { const slice_info = @typeInfo(@TypeOf(old_mem)).pointer; + if (slice_info.size != .slice) { + const slice: SliceType(@TypeOf(old_mem)) = old_mem; // coerce *[len]T to []T + return reallocAdvanced(self, slice, new_n, return_address); + } comptime assert(slice_info.size == .slice); const T = slice_info.child; if (old_mem.len == 0) { @@ -446,9 +477,10 @@ pub fn reallocAdvanced( pub fn free(self: Allocator, memory: anytype) void { const slice_info = @typeInfo(@TypeOf(memory)).pointer; if (slice_info.size != .slice) { - // slicing with comptime-known start and end results in *[len]T, which may be free'd - comptime assert(slice_info.size == .one and @typeInfo(slice_info.child) == .array); + const slice: SliceType(@TypeOf(memory)) = memory; // coerce *[len]T to []T + return free(self, slice); } + comptime assert(slice_info.size == .slice); const bytes: []u8 = @ptrCast(@constCast(mem.absorbSentinel(memory))); if (bytes.len == 0) return; @memset(bytes, undefined); @@ -591,3 +623,9 @@ test failing { try std.testing.expectError(error.OutOfMemory, f.alloc(u8, std.math.maxInt(usize))); try std.testing.expectError(error.OutOfMemory, f.allocSentinel(u8, std.math.maxInt(usize) - 1, 0)); } + +test "free single-pointer to array" { + const allocator = std.testing.allocator; + const bytes = allocator.alloc(u32, 128) catch return error.SkipZigTest; + allocator.free(bytes.ptr[0..128]); +} -- 2.54.0 From 1e5d24a009ffef49f0888b3d6ea46aec8a82d40b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 5 Aug 2026 14:48:54 -0700 Subject: [PATCH 2/7] std: extract mem.Allocator.SliceType to meta.Slice --- lib/std/mem/Allocator.zig | 34 ++++++++-------------------------- lib/std/meta.zig | 34 +++++++++++++++++++++++++++++----- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/lib/std/mem/Allocator.zig b/lib/std/mem/Allocator.zig index e442427169f56898da818f11e3587ebe4d348dc2..12d96f6030c030a11f1fd81419b6f10168b7cfa3 100644 --- a/lib/std/mem/Allocator.zig +++ b/lib/std/mem/Allocator.zig @@ -8,6 +8,7 @@ const assert = std.debug.assert; const math = std.math; const mem = std.mem; const Alignment = std.mem.Alignment; +const Slice = std.meta.Slice; pub const Error = error{OutOfMemory}; pub const Log2Align = math.Log2Int(usize); @@ -305,25 +306,6 @@ pub fn allocBytesAligned( return @alignCast(byte_ptr); } -fn SliceType(comptime Pointer: type) type { - const info = @typeInfo(Pointer).pointer; - switch (info.size) { - .slice => return Pointer, - .one => { - const child_info = @typeInfo(info.child); - comptime assert(child_info == .array); - const sentinel_ptr: ?*const child_info.array.child = @ptrCast(@alignCast(child_info.array.sentinel_ptr)); - return @Pointer( - .slice, - info.attrs, - child_info.array.child, - if (sentinel_ptr) |ptr| ptr.* else null, - ); - }, - else => unreachable, - } -} - /// Request to modify the size of an allocation. /// /// It is guaranteed to not move the pointer, however the allocator @@ -336,7 +318,7 @@ fn SliceType(comptime Pointer: type) type { pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool { const slice_info = @typeInfo(@TypeOf(allocation)).pointer; if (slice_info.size != .slice) { - const slice: SliceType(@TypeOf(allocation)) = allocation; // coerce *[len]T to []T + const slice: Slice(@TypeOf(allocation)) = allocation; // coerce *[len]T to []T return resize(self, slice, new_len); } comptime assert(slice_info.size == .slice); @@ -377,10 +359,10 @@ pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool { /// `new_len` may be zero, in which case the allocation is freed. /// /// If the allocation's elements' type is zero bytes sized, `allocation.len` is set to `new_len`. -pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?SliceType(@TypeOf(allocation)) { +pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?Slice(@TypeOf(allocation)) { const slice_info = @typeInfo(@TypeOf(allocation)).pointer; if (slice_info.size != .slice) { - const slice: SliceType(@TypeOf(allocation)) = allocation; // coerce *[len]T to []T + const slice: Slice(@TypeOf(allocation)) = allocation; // coerce *[len]T to []T return remap(self, slice, new_len); } comptime assert(slice_info.size == .slice); @@ -426,7 +408,7 @@ pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?SliceType(@T /// do the realloc more efficiently than the caller /// * `resize` which returns `false` when the `Allocator` implementation cannot /// change the size without relocating the allocation. -pub fn realloc(self: Allocator, old_mem: anytype, new_n: usize) Error!SliceType(@TypeOf(old_mem)) { +pub fn realloc(self: Allocator, old_mem: anytype, new_n: usize) Error!Slice(@TypeOf(old_mem)) { return self.reallocAdvanced(old_mem, new_n, @returnAddress()); } @@ -435,10 +417,10 @@ pub fn reallocAdvanced( old_mem: anytype, new_n: usize, return_address: usize, -) Error!SliceType(@TypeOf(old_mem)) { +) Error!Slice(@TypeOf(old_mem)) { const slice_info = @typeInfo(@TypeOf(old_mem)).pointer; if (slice_info.size != .slice) { - const slice: SliceType(@TypeOf(old_mem)) = old_mem; // coerce *[len]T to []T + const slice: Slice(@TypeOf(old_mem)) = old_mem; // coerce *[len]T to []T return reallocAdvanced(self, slice, new_n, return_address); } comptime assert(slice_info.size == .slice); @@ -477,7 +459,7 @@ pub fn reallocAdvanced( pub fn free(self: Allocator, memory: anytype) void { const slice_info = @typeInfo(@TypeOf(memory)).pointer; if (slice_info.size != .slice) { - const slice: SliceType(@TypeOf(memory)) = memory; // coerce *[len]T to []T + const slice: Slice(@TypeOf(memory)) = memory; // coerce *[len]T to []T return free(self, slice); } comptime assert(slice_info.size == .slice); diff --git a/lib/std/meta.zig b/lib/std/meta.zig index cfeb6a758524a4e0e5a3f9e6dcac57fd029123c6..76f0ae867f66dd158e30521bbd2c1e7e4ef53f9d 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -1,10 +1,9 @@ const builtin = @import("builtin"); + const std = @import("std.zig"); -const debug = std.debug; +const assert = std.debug.assert; const mem = std.mem; -const math = std.math; const testing = std.testing; -const root = @import("root"); pub const TrailerFlags = @import("meta/trailer_flags.zig").TrailerFlags; @@ -821,8 +820,8 @@ pub fn isError(error_union: anytype) bool { } test isError { - try std.testing.expect(isError(math.divTrunc(u8, 5, 0))); - try std.testing.expect(!isError(math.divTrunc(u8, 5, 5))); + try std.testing.expect(isError(std.math.divTrunc(u8, 5, 0))); + try std.testing.expect(!isError(std.math.divTrunc(u8, 5, 5))); } /// Returns true if a type has a namespace and the namespace contains `name`; @@ -1070,3 +1069,28 @@ test hasUniqueRepresentation { try testing.expect(hasUniqueRepresentation(StructWithComptimeFields)); } + +/// Given a pointer type, type-erases the array length if present, returning an +/// equivalent pointer type that is always a slice. +pub fn Slice(comptime Pointer: type) type { + const info = @typeInfo(Pointer).pointer; + switch (info.size) { + .slice => return Pointer, + .one => { + const child_info = @typeInfo(info.child); + comptime assert(child_info == .array); + const sentinel_ptr: ?*const child_info.array.child = @ptrCast(@alignCast(child_info.array.sentinel_ptr)); + return @Pointer( + .slice, + info.attrs, + child_info.array.child, + if (sentinel_ptr) |ptr| ptr.* else null, + ); + }, + else => unreachable, + } +} + +test Slice { + try testing.expectEqual([]i32, Slice(*[10]i32)); +} -- 2.54.0 From 009604d690aeaac6957d4646a815bc8762e84757 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 5 Aug 2026 15:11:22 -0700 Subject: [PATCH 3/7] Allocator: simplify; avoid unnecessary recursion --- lib/std/mem/Allocator.zig | 46 ++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/lib/std/mem/Allocator.zig b/lib/std/mem/Allocator.zig index 12d96f6030c030a11f1fd81419b6f10168b7cfa3..08bfe689ff4eca5f1b3cb434757371223cfc8916 100644 --- a/lib/std/mem/Allocator.zig +++ b/lib/std/mem/Allocator.zig @@ -316,18 +316,15 @@ pub fn allocBytesAligned( /// /// `new_len` may be zero, in which case the allocation is freed. pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool { - const slice_info = @typeInfo(@TypeOf(allocation)).pointer; - if (slice_info.size != .slice) { - const slice: Slice(@TypeOf(allocation)) = allocation; // coerce *[len]T to []T - return resize(self, slice, new_len); - } - comptime assert(slice_info.size == .slice); + const SliceType = Slice(@TypeOf(allocation)); + const slice: SliceType = allocation; // coerce *[len]T to []T + const slice_info = @typeInfo(SliceType).pointer; const T = slice_info.child; if (new_len == 0) { - self.free(allocation); + self.free(slice); return true; } - if (allocation.len == 0) { + if (slice.len == 0) { return false; } const old_memory: []u8 = @ptrCast(@constCast(mem.absorbSentinel(allocation))); @@ -360,27 +357,24 @@ pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool { /// /// If the allocation's elements' type is zero bytes sized, `allocation.len` is set to `new_len`. pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?Slice(@TypeOf(allocation)) { - const slice_info = @typeInfo(@TypeOf(allocation)).pointer; - if (slice_info.size != .slice) { - const slice: Slice(@TypeOf(allocation)) = allocation; // coerce *[len]T to []T - return remap(self, slice, new_len); - } - comptime assert(slice_info.size == .slice); + const SliceType = Slice(@TypeOf(allocation)); + const slice: SliceType = allocation; // coerce *[len]T to []T + const slice_info = @typeInfo(SliceType).pointer; const T = slice_info.child; if (new_len == 0) { - self.free(allocation); - return allocation[0..0]; + self.free(slice); + return slice[0..0]; } - if (allocation.len == 0) { + if (slice.len == 0) { return null; } if (@sizeOf(T) == 0) { - var new_memory = allocation; + var new_memory = slice; new_memory.len = new_len; return new_memory; } - const old_memory: []u8 = @ptrCast(@constCast(mem.absorbSentinel(allocation))); + const old_memory: []u8 = @ptrCast(@constCast(mem.absorbSentinel(slice))); // I would like to use saturating multiplication here, but LLVM cannot lower it // on WebAssembly: https://github.com/ziglang/zig/issues/9660 //const new_len_bytes = new_len *| @sizeOf(T); @@ -418,25 +412,23 @@ pub fn reallocAdvanced( new_n: usize, return_address: usize, ) Error!Slice(@TypeOf(old_mem)) { - const slice_info = @typeInfo(@TypeOf(old_mem)).pointer; - if (slice_info.size != .slice) { - const slice: Slice(@TypeOf(old_mem)) = old_mem; // coerce *[len]T to []T - return reallocAdvanced(self, slice, new_n, return_address); - } + const SliceType = Slice(@TypeOf(old_mem)); + const slice: SliceType = old_mem; // coerce *[len]T to []T + const slice_info = @typeInfo(SliceType).pointer; comptime assert(slice_info.size == .slice); const T = slice_info.child; - if (old_mem.len == 0) { + if (slice.len == 0) { return self.allocAdvancedWithRetAddr(T, .fromByteUnitsOptional(slice_info.attrs.@"align"), new_n, return_address); } if (new_n == 0) { - self.free(old_mem); + self.free(slice); const alignment = slice_info.attrs.@"align" orelse @alignOf(T); const addr = comptime std.mem.alignBackward(usize, math.maxInt(usize), alignment); const ptr: *align(alignment) [0]T = @ptrFromInt(addr); return ptr; } - const old_byte_slice: []u8 = @ptrCast(@constCast(mem.absorbSentinel(old_mem))); + const old_byte_slice: []u8 = @ptrCast(@constCast(mem.absorbSentinel(slice))); const byte_count = math.mul(usize, @sizeOf(T), new_n) catch return error.OutOfMemory; // Note: can't set shrunk memory to undefined as memory shouldn't be modified on realloc failure if (self.rawRemap(old_byte_slice, .fromByteUnits(slice_info.attrs.@"align" orelse @alignOf(T)), byte_count, return_address)) |p| { -- 2.54.0 From b470382173c6be27f6cdd46c3adce0e59f2b3675 Mon Sep 17 00:00:00 2001 From: Robbie Lyman Date: Thu, 6 Aug 2026 20:35:17 -0400 Subject: [PATCH 4/7] feat(mem): allow absorbSentinel on *[n]T --- lib/std/mem.zig | 38 +++++++++++------ lib/std/mem/Allocator.zig | 90 +++++++++++++++++++++++++-------------- lib/std/meta.zig | 22 ++++++++++ 3 files changed, 107 insertions(+), 43 deletions(-) diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 55b9019c4f4d95cfce0f271de52d12a856f9aea8..f6b2af68f914e5d31da6fc89f3702567d99fc1ac 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -9,6 +9,7 @@ const assert = debug.assert; const math = std.math; const testing = std.testing; const Endian = std.lang.Endian; +const AbsorbSentinel = std.meta.AbsorbSentinel; /// The standard library currently thoroughly depends on byte size /// being 8 bits. (see the use of u8 throughout allocation code as @@ -4738,22 +4739,28 @@ test "sliceAsBytes preserves pointer attributes" { try testing.expectEqual(in_attrs.@"align", out_attrs.@"align"); } -fn AbsorbSentinelReturnType(comptime Slice: type) type { - const info = @typeInfo(Slice).pointer; - assert(info.size == .slice); - return @Pointer(.slice, info.attrs, info.child, null); -} - /// If the provided slice is not sentinel terminated, do nothing and return that slice. /// If it is sentinel-terminated, return a non-sentinel-terminated slice with the /// length increased by one to include the absorbed sentinel element. -pub fn absorbSentinel(slice: anytype) AbsorbSentinelReturnType(@TypeOf(slice)) { +pub fn absorbSentinel(slice: anytype) AbsorbSentinel(@TypeOf(slice)) { const info = @typeInfo(@TypeOf(slice)).pointer; - comptime assert(info.size == .slice); - if (info.sentinel_ptr == null) { - return slice; - } else { - return slice.ptr[0 .. slice.len + 1]; + switch (info.size) { + .slice => { + if (info.sentinel_ptr == null) { + return slice; + } else { + return slice.ptr[0 .. slice.len + 1]; + } + }, + .one => { + const child_info = @typeInfo(info.child).array; + if (child_info.sentinel_ptr == null) { + return slice; + } else { + return slice[0 .. child_info.len + 1]; + } + }, + else => unreachable, } } @@ -4762,21 +4769,28 @@ test absorbSentinel { var buffer: [3:0]u8 = .{ 1, 2, 3 }; const foo: [:0]const u8 = &buffer; const bar: []const u8 = &buffer; + const baz: *const [3:0]u8 = &buffer; try testing.expectEqual([]const u8, @TypeOf(absorbSentinel(foo))); try testing.expectEqual([]const u8, @TypeOf(absorbSentinel(bar))); + try testing.expectEqual(*const [4]u8, @TypeOf(absorbSentinel(baz))); try testing.expectEqualSlices(u8, &.{ 1, 2, 3, 0 }, absorbSentinel(foo)); try testing.expectEqualSlices(u8, &.{ 1, 2, 3 }, absorbSentinel(bar)); + try testing.expectEqualSlices(u8, &.{ 1, 2, 3, 0 }, absorbSentinel(baz)); } { var buffer: [3:0]u8 = .{ 1, 2, 3 }; const foo: [:0]u8 = &buffer; const bar: []u8 = &buffer; + const baz: *[3:0]u8 = &buffer; try testing.expectEqual([]u8, @TypeOf(absorbSentinel(foo))); try testing.expectEqual([]u8, @TypeOf(absorbSentinel(bar))); + try testing.expectEqual(*[4]u8, @TypeOf(absorbSentinel(baz))); var expected_foo = [_]u8{ 1, 2, 3, 0 }; try testing.expectEqualSlices(u8, &expected_foo, absorbSentinel(foo)); var expected_bar = [_]u8{ 1, 2, 3 }; try testing.expectEqualSlices(u8, &expected_bar, absorbSentinel(bar)); + var expected_baz = [_]u8{ 1, 2, 3, 0 }; + try testing.expectEqualSlices(u8, &expected_baz, absorbSentinel(baz)); } } diff --git a/lib/std/mem/Allocator.zig b/lib/std/mem/Allocator.zig index 08bfe689ff4eca5f1b3cb434757371223cfc8916..f16944bb7a934071acb8c89808f1560c2e08df9c 100644 --- a/lib/std/mem/Allocator.zig +++ b/lib/std/mem/Allocator.zig @@ -9,6 +9,7 @@ const math = std.math; const mem = std.mem; const Alignment = std.mem.Alignment; const Slice = std.meta.Slice; +const AbsorbSentinel = std.meta.AbsorbSentinel; pub const Error = error{OutOfMemory}; pub const Log2Align = math.Log2Int(usize); @@ -316,15 +317,16 @@ pub fn allocBytesAligned( /// /// `new_len` may be zero, in which case the allocation is freed. pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool { - const SliceType = Slice(@TypeOf(allocation)); - const slice: SliceType = allocation; // coerce *[len]T to []T - const slice_info = @typeInfo(SliceType).pointer; - const T = slice_info.child; + const slice_info = @typeInfo(@TypeOf(allocation)).pointer; + const T = if (slice_info.size != .slice) comptime T: { + assert(slice_info.size == .one); + break :T @typeInfo(slice_info.child).array.child; + } else slice_info.child; if (new_len == 0) { - self.free(slice); + self.free(allocation); return true; } - if (slice.len == 0) { + if (allocation.len == 0) { return false; } const old_memory: []u8 = @ptrCast(@constCast(mem.absorbSentinel(allocation))); @@ -356,25 +358,26 @@ pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool { /// `new_len` may be zero, in which case the allocation is freed. /// /// If the allocation's elements' type is zero bytes sized, `allocation.len` is set to `new_len`. -pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?Slice(@TypeOf(allocation)) { - const SliceType = Slice(@TypeOf(allocation)); - const slice: SliceType = allocation; // coerce *[len]T to []T - const slice_info = @typeInfo(SliceType).pointer; - const T = slice_info.child; +pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?Slice(AbsorbSentinel(@TypeOf(allocation))) { + const slice_info = @typeInfo(@TypeOf(allocation)).pointer; + const T = if (slice_info.size != .slice) comptime T: { + assert(slice_info.size == .one); + break :T @typeInfo(slice_info.child).array.child; + } else slice_info.child; if (new_len == 0) { - self.free(slice); - return slice[0..0]; + self.free(allocation); + return allocation[0..0]; } - if (slice.len == 0) { + if (allocation.len == 0) { return null; } if (@sizeOf(T) == 0) { - var new_memory = slice; + var new_memory = allocation; new_memory.len = new_len; return new_memory; } - const old_memory: []u8 = @ptrCast(@constCast(mem.absorbSentinel(slice))); + const old_memory: []u8 = @ptrCast(@constCast(mem.absorbSentinel(allocation))); // I would like to use saturating multiplication here, but LLVM cannot lower it // on WebAssembly: https://github.com/ziglang/zig/issues/9660 //const new_len_bytes = new_len *| @sizeOf(T); @@ -402,7 +405,7 @@ pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?Slice(@TypeO /// do the realloc more efficiently than the caller /// * `resize` which returns `false` when the `Allocator` implementation cannot /// change the size without relocating the allocation. -pub fn realloc(self: Allocator, old_mem: anytype, new_n: usize) Error!Slice(@TypeOf(old_mem)) { +pub fn realloc(self: Allocator, old_mem: anytype, new_n: usize) Error!Slice(AbsorbSentinel(@TypeOf(old_mem))) { return self.reallocAdvanced(old_mem, new_n, @returnAddress()); } @@ -411,24 +414,24 @@ pub fn reallocAdvanced( old_mem: anytype, new_n: usize, return_address: usize, -) Error!Slice(@TypeOf(old_mem)) { - const SliceType = Slice(@TypeOf(old_mem)); - const slice: SliceType = old_mem; // coerce *[len]T to []T - const slice_info = @typeInfo(SliceType).pointer; - comptime assert(slice_info.size == .slice); - const T = slice_info.child; - if (slice.len == 0) { +) Error!Slice(AbsorbSentinel(@TypeOf(old_mem))) { + const slice_info = @typeInfo(@TypeOf(old_mem)).pointer; + const T = if (slice_info.size != .slice) comptime T: { + assert(slice_info.size == .one); + break :T @typeInfo(slice_info.child).array.child; + } else slice_info.child; + if (old_mem.len == 0) { return self.allocAdvancedWithRetAddr(T, .fromByteUnitsOptional(slice_info.attrs.@"align"), new_n, return_address); } if (new_n == 0) { - self.free(slice); + self.free(old_mem); const alignment = slice_info.attrs.@"align" orelse @alignOf(T); const addr = comptime std.mem.alignBackward(usize, math.maxInt(usize), alignment); const ptr: *align(alignment) [0]T = @ptrFromInt(addr); return ptr; } - const old_byte_slice: []u8 = @ptrCast(@constCast(mem.absorbSentinel(slice))); + const old_byte_slice: []u8 = @ptrCast(@constCast(mem.absorbSentinel(old_mem))); const byte_count = math.mul(usize, @sizeOf(T), new_n) catch return error.OutOfMemory; // Note: can't set shrunk memory to undefined as memory shouldn't be modified on realloc failure if (self.rawRemap(old_byte_slice, .fromByteUnits(slice_info.attrs.@"align" orelse @alignOf(T)), byte_count, return_address)) |p| { @@ -451,10 +454,8 @@ pub fn reallocAdvanced( pub fn free(self: Allocator, memory: anytype) void { const slice_info = @typeInfo(@TypeOf(memory)).pointer; if (slice_info.size != .slice) { - const slice: Slice(@TypeOf(memory)) = memory; // coerce *[len]T to []T - return free(self, slice); + assert(slice_info.size == .one and @typeInfo(slice_info.child) == .array); } - comptime assert(slice_info.size == .slice); const bytes: []u8 = @ptrCast(@constCast(mem.absorbSentinel(memory))); if (bytes.len == 0) return; @memset(bytes, undefined); @@ -600,6 +601,33 @@ test failing { test "free single-pointer to array" { const allocator = std.testing.allocator; - const bytes = allocator.alloc(u32, 128) catch return error.SkipZigTest; - allocator.free(bytes.ptr[0..128]); + { + const slice = allocator.alloc(u32, 128) catch return error.SkipZigTest; + slice[127] = 0; + const ptr = slice[0..127 :0]; + allocator.free(ptr); + } + { + const slice = allocator.alloc(u32, 128) catch return error.SkipZigTest; + slice[127] = 0; + const ptr = slice[0..127 :0]; + if (allocator.resize(ptr, 16)) { + allocator.free(ptr[0..16]); + } else allocator.free(ptr); + } + { + const slice = allocator.alloc(u32, 128) catch return error.SkipZigTest; + slice[127] = 0; + const ptr = slice[0..127 :0]; + if (allocator.remap(ptr, 16)) |new| { + allocator.free(new); + } else allocator.free(ptr); + } + { + const slice = allocator.alloc(u32, 128) catch return error.SkipZigTest; + slice[127] = 0; + const ptr = slice[0..127 :0]; + const new = allocator.realloc(ptr, 16) catch return error.SkipZigTest; + allocator.free(new); + } } diff --git a/lib/std/meta.zig b/lib/std/meta.zig index 76f0ae867f66dd158e30521bbd2c1e7e4ef53f9d..edc4a5c4e8eca6de0891b5435e644f0917a1ed28 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -1091,6 +1091,28 @@ pub fn Slice(comptime Pointer: type) type { } } +/// Given a pointer type, removes the sentinel if present, returning an +/// equivalent pointer type with no sentinel +pub fn AbsorbSentinel(comptime Pointer: type) type { + const info = @typeInfo(Pointer).pointer; + switch (info.size) { + .slice => return @Pointer(.slice, info.attrs, info.child, null), + .one => { + const child_info = @typeInfo(info.child).array; + if (child_info.sentinel_ptr == null) { + return Pointer; + } else { + return @Pointer(.one, info.attrs, [child_info.len + 1]child_info.child, null); + } + }, + else => unreachable, + } +} + test Slice { try testing.expectEqual([]i32, Slice(*[10]i32)); } + +test AbsorbSentinel { + try testing.expectEqual(*[5]u32, AbsorbSentinel(*[4:0]u32)); +} -- 2.54.0 From 89069b54d9f3a83ac675c6a18df2c4a52d019835 Mon Sep 17 00:00:00 2001 From: Robbie Lyman Date: Thu, 6 Aug 2026 22:26:40 -0400 Subject: [PATCH 5/7] fix(Allocator.free): restore compile error --- lib/std/mem/Allocator.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/mem/Allocator.zig b/lib/std/mem/Allocator.zig index f16944bb7a934071acb8c89808f1560c2e08df9c..1f11c637b0168068004019b8839458f021d679f5 100644 --- a/lib/std/mem/Allocator.zig +++ b/lib/std/mem/Allocator.zig @@ -454,7 +454,7 @@ pub fn reallocAdvanced( pub fn free(self: Allocator, memory: anytype) void { const slice_info = @typeInfo(@TypeOf(memory)).pointer; if (slice_info.size != .slice) { - assert(slice_info.size == .one and @typeInfo(slice_info.child) == .array); + comptime assert(slice_info.size == .one and @typeInfo(slice_info.child) == .array); } const bytes: []u8 = @ptrCast(@constCast(mem.absorbSentinel(memory))); if (bytes.len == 0) return; -- 2.54.0 From 16fbd314dc9590077267fc373b76b7c7b977977a Mon Sep 17 00:00:00 2001 From: Robbie Lyman Date: Thu, 6 Aug 2026 23:44:45 -0400 Subject: [PATCH 6/7] fix(Allocator): test style fixes --- lib/std/mem/Allocator.zig | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/std/mem/Allocator.zig b/lib/std/mem/Allocator.zig index 1f11c637b0168068004019b8839458f021d679f5..d50c7c1764bfe5b38a78d0761bb4983736839e41 100644 --- a/lib/std/mem/Allocator.zig +++ b/lib/std/mem/Allocator.zig @@ -602,32 +602,32 @@ test failing { test "free single-pointer to array" { const allocator = std.testing.allocator; { - const slice = allocator.alloc(u32, 128) catch return error.SkipZigTest; - slice[127] = 0; - const ptr = slice[0..127 :0]; + const allocation = try allocator.alloc(u32, 128); + allocation[127] = 0; + const ptr: *[127:0]u32 = allocation[0..127 :0]; allocator.free(ptr); } { - const slice = allocator.alloc(u32, 128) catch return error.SkipZigTest; - slice[127] = 0; - const ptr = slice[0..127 :0]; + const allocation = try allocator.alloc(u32, 128); + allocation[127] = 0; + const ptr: *[127:0]u32 = allocation[0..127 :0]; if (allocator.resize(ptr, 16)) { allocator.free(ptr[0..16]); } else allocator.free(ptr); } { - const slice = allocator.alloc(u32, 128) catch return error.SkipZigTest; - slice[127] = 0; - const ptr = slice[0..127 :0]; + const allocation = try allocator.alloc(u32, 128); + allocation[127] = 0; + const ptr: *[127:0]u32 = allocation[0..127 :0]; if (allocator.remap(ptr, 16)) |new| { allocator.free(new); } else allocator.free(ptr); } { - const slice = allocator.alloc(u32, 128) catch return error.SkipZigTest; - slice[127] = 0; - const ptr = slice[0..127 :0]; - const new = allocator.realloc(ptr, 16) catch return error.SkipZigTest; + const allocation = try allocator.alloc(u32, 128); + allocation[127] = 0; + const ptr: *[127:0]u32 = allocation[0..127 :0]; + const new = try allocator.realloc(ptr, 16); allocator.free(new); } } -- 2.54.0 From c0924842744fea67df49c9942b99d855b83578e5 Mon Sep 17 00:00:00 2001 From: Robbie Lyman Date: Fri, 7 Aug 2026 11:54:36 -0400 Subject: [PATCH 7/7] fix(Allocator): test leak --- lib/std/mem/Allocator.zig | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/std/mem/Allocator.zig b/lib/std/mem/Allocator.zig index d50c7c1764bfe5b38a78d0761bb4983736839e41..98164b5b21abf095f3c65c9cd0abebe21b00193d 100644 --- a/lib/std/mem/Allocator.zig +++ b/lib/std/mem/Allocator.zig @@ -627,7 +627,10 @@ test "free single-pointer to array" { const allocation = try allocator.alloc(u32, 128); allocation[127] = 0; const ptr: *[127:0]u32 = allocation[0..127 :0]; - const new = try allocator.realloc(ptr, 16); - allocator.free(new); + if (allocator.realloc(ptr, 16)) |new| { + allocator.free(new); + } else |_| { + allocator.free(allocation); + } } } -- 2.54.0