1//! The standard memory allocation interface.
2const Allocator = @This();
3
4const builtin = @import("builtin");
5
6const std = @import("../std.zig");
7const assert = std.debug.assert;
8const math = std.math;
9const mem = std.mem;
10const Alignment = std.mem.Alignment;
11const Slice = std.meta.Slice;
12const AbsorbSentinel = std.meta.AbsorbSentinel;
13
14pub const Error = error{OutOfMemory};
15pub const Log2Align = math.Log2Int(usize);
16
17/// The type erased pointer to the allocator implementation.
18///
19/// Any comparison of this field may result in illegal behavior, since it may
20/// be set to `undefined` in cases where the allocator implementation does not
21/// have any associated state.
22ptr: *anyopaque,
23vtable: *const VTable,
24
25pub const VTable = struct {
26 /// Return a pointer to `len` bytes with specified `alignment`, or return
27 /// `null` indicating the allocation failed.
28 ///
29 /// `new_len` must be greater than zero.
30 ///
31 /// `ret_addr` is optionally provided as the first return address of the
32 /// allocation call stack. If the value is `0` it means no return address
33 /// has been provided.
34 alloc: *const fn (*anyopaque, len: usize, alignment: Alignment, ret_addr: usize) ?[*]u8,
35
36 /// Attempt to expand or shrink memory in place.
37 ///
38 /// `memory.len` must equal the length requested from the most recent
39 /// successful call to `alloc`, `resize`, or `remap`. `alignment` must
40 /// equal the same value that was passed as the `alignment` parameter to
41 /// the original `alloc` call.
42 ///
43 /// A result of `true` indicates the resize was successful and the
44 /// allocation now has the same address but a size of `new_len`. `false`
45 /// indicates the resize could not be completed without moving the
46 /// allocation to a different address.
47 ///
48 /// `new_len` must be greater than zero.
49 ///
50 /// `ret_addr` is optionally provided as the first return address of the
51 /// allocation call stack. If the value is `0` it means no return address
52 /// has been provided.
53 resize: *const fn (*anyopaque, memory: []u8, alignment: Alignment, new_len: usize, ret_addr: usize) bool,
54
55 /// Attempt to expand or shrink memory, allowing relocation.
56 ///
57 /// `memory.len` must equal the length requested from the most recent
58 /// successful call to `alloc`, `resize`, or `remap`. `alignment` must
59 /// equal the same value that was passed as the `alignment` parameter to
60 /// the original `alloc` call.
61 ///
62 /// A non-`null` return value indicates the resize was successful. The
63 /// allocation may have same address, or may have been relocated. In either
64 /// case, the allocation now has size of `new_len`. A `null` return value
65 /// indicates that the resize would be equivalent to allocating new memory,
66 /// copying the bytes from the old memory, and then freeing the old memory.
67 /// In such case, it is more efficient for the caller to perform the copy.
68 ///
69 /// `new_len` must be greater than zero.
70 ///
71 /// `ret_addr` is optionally provided as the first return address of the
72 /// allocation call stack. If the value is `0` it means no return address
73 /// has been provided.
74 remap: *const fn (*anyopaque, memory: []u8, alignment: Alignment, new_len: usize, ret_addr: usize) ?[*]u8,
75
76 /// Free and invalidate a region of memory.
77 ///
78 /// `memory.len` must equal the length requested from the most recent
79 /// successful call to `alloc`, `resize`, or `remap`. `alignment` must
80 /// equal the same value that was passed as the `alignment` parameter to
81 /// the original `alloc` call.
82 ///
83 /// `ret_addr` is optionally provided as the first return address of the
84 /// allocation call stack. If the value is `0` it means no return address
85 /// has been provided.
86 free: *const fn (*anyopaque, memory: []u8, alignment: Alignment, ret_addr: usize) void,
87};
88
89pub fn noAlloc(
90 self: *anyopaque,
91 len: usize,
92 alignment: Alignment,
93 ret_addr: usize,
94) ?[*]u8 {
95 _ = self;
96 _ = len;
97 _ = alignment;
98 _ = ret_addr;
99 return null;
100}
101
102pub fn noResize(
103 self: *anyopaque,
104 memory: []u8,
105 alignment: Alignment,
106 new_len: usize,
107 ret_addr: usize,
108) bool {
109 _ = self;
110 _ = memory;
111 _ = alignment;
112 _ = new_len;
113 _ = ret_addr;
114 return false;
115}
116
117pub fn noRemap(
118 self: *anyopaque,
119 memory: []u8,
120 alignment: Alignment,
121 new_len: usize,
122 ret_addr: usize,
123) ?[*]u8 {
124 _ = self;
125 _ = memory;
126 _ = alignment;
127 _ = new_len;
128 _ = ret_addr;
129 return null;
130}
131
132pub fn noFree(
133 self: *anyopaque,
134 memory: []u8,
135 alignment: Alignment,
136 ret_addr: usize,
137) void {
138 _ = self;
139 _ = memory;
140 _ = alignment;
141 _ = ret_addr;
142}
143
144/// This function is not intended to be called except from within the
145/// implementation of an `Allocator`.
146pub inline fn rawAlloc(a: Allocator, len: usize, alignment: Alignment, ret_addr: usize) ?[*]u8 {
147 return a.vtable.alloc(a.ptr, len, alignment, ret_addr);
148}
149
150/// This function is not intended to be called except from within the
151/// implementation of an `Allocator`.
152pub inline fn rawResize(a: Allocator, memory: []u8, alignment: Alignment, new_len: usize, ret_addr: usize) bool {
153 return a.vtable.resize(a.ptr, memory, alignment, new_len, ret_addr);
154}
155
156/// This function is not intended to be called except from within the
157/// implementation of an `Allocator`.
158pub inline fn rawRemap(a: Allocator, memory: []u8, alignment: Alignment, new_len: usize, ret_addr: usize) ?[*]u8 {
159 return a.vtable.remap(a.ptr, memory, alignment, new_len, ret_addr);
160}
161
162/// This function is not intended to be called except from within the
163/// implementation of an `Allocator`.
164pub inline fn rawFree(a: Allocator, memory: []u8, alignment: Alignment, ret_addr: usize) void {
165 return a.vtable.free(a.ptr, memory, alignment, ret_addr);
166}
167
168/// Returns a pointer to undefined memory.
169/// Call `destroy` with the result to free the memory.
170pub fn create(a: Allocator, comptime T: type) Error!*T {
171 if (@sizeOf(T) == 0) {
172 const ptr = comptime std.mem.alignBackward(usize, math.maxInt(usize), @alignOf(T));
173 return @ptrFromInt(ptr);
174 }
175 const ptr: *T = @ptrCast(try a.allocBytesAligned(.of(T), @sizeOf(T), @returnAddress()));
176 return ptr;
177}
178
179/// `ptr` should be the return value of `create`, or otherwise
180/// have the same address and alignment property.
181pub fn destroy(self: Allocator, ptr: anytype) void {
182 const info = @typeInfo(@TypeOf(ptr)).pointer;
183 if (info.size != .one) @compileError("ptr must be a single item pointer");
184 const T = info.child;
185 if (@sizeOf(T) == 0) return;
186 const non_const_ptr = @as([*]u8, @ptrCast(@constCast(ptr)));
187 self.rawFree(
188 non_const_ptr[0..@sizeOf(T)],
189 .fromByteUnits(info.attrs.@"align" orelse @alignOf(T)),
190 @returnAddress(),
191 );
192}
193
194/// Allocates an array of `n` items of type `T` and sets all the
195/// items to `undefined`. Depending on the Allocator
196/// implementation, it may be required to call `free` once the
197/// memory is no longer needed, to avoid a resource leak. If the
198/// `Allocator` implementation is unknown, then correct code will
199/// call `free` when done.
200///
201/// For allocating a single item, see `create`.
202pub fn alloc(self: Allocator, comptime T: type, n: usize) Error![]T {
203 return self.allocAdvancedWithRetAddr(T, null, n, @returnAddress());
204}
205
206pub fn allocWithOptions(
207 self: Allocator,
208 comptime Elem: type,
209 n: usize,
210 /// null means naturally aligned
211 comptime optional_alignment: ?Alignment,
212 comptime optional_sentinel: ?Elem,
213) Error!AllocWithOptionsPayload(Elem, optional_alignment, optional_sentinel) {
214 return self.allocWithOptionsRetAddr(Elem, n, optional_alignment, optional_sentinel, @returnAddress());
215}
216
217pub fn allocWithOptionsRetAddr(
218 self: Allocator,
219 comptime Elem: type,
220 n: usize,
221 /// null means naturally aligned
222 comptime optional_alignment: ?Alignment,
223 comptime optional_sentinel: ?Elem,
224 return_address: usize,
225) Error!AllocWithOptionsPayload(Elem, optional_alignment, optional_sentinel) {
226 if (optional_sentinel) |sentinel| {
227 const ptr = try self.allocAdvancedWithRetAddr(Elem, optional_alignment, n + 1, return_address);
228 ptr[n] = sentinel;
229 return ptr[0..n :sentinel];
230 } else {
231 return self.allocAdvancedWithRetAddr(Elem, optional_alignment, n, return_address);
232 }
233}
234
235fn AllocWithOptionsPayload(comptime Elem: type, comptime alignment: ?Alignment, comptime sentinel: ?Elem) type {
236 if (sentinel) |s| {
237 return [:s]align(if (alignment) |a| a.toByteUnits() else @alignOf(Elem)) Elem;
238 } else {
239 return []align(if (alignment) |a| a.toByteUnits() else @alignOf(Elem)) Elem;
240 }
241}
242
243/// Allocates an array of `n + 1` items of type `T` and sets the first `n`
244/// items to `undefined` and the last item to `sentinel`. Depending on the
245/// Allocator implementation, it may be required to call `free` once the
246/// memory is no longer needed, to avoid a resource leak. If the
247/// `Allocator` implementation is unknown, then correct code will
248/// call `free` when done.
249///
250/// For allocating a single item, see `create`.
251pub fn allocSentinel(
252 self: Allocator,
253 comptime Elem: type,
254 n: usize,
255 comptime sentinel: Elem,
256) Error![:sentinel]Elem {
257 return self.allocWithOptionsRetAddr(Elem, n, null, sentinel, @returnAddress());
258}
259
260pub fn alignedAlloc(
261 self: Allocator,
262 comptime T: type,
263 /// null means naturally aligned
264 comptime alignment: ?Alignment,
265 n: usize,
266) Error![]align(if (alignment) |a| a.toByteUnits() else @alignOf(T)) T {
267 return self.allocAdvancedWithRetAddr(T, alignment, n, @returnAddress());
268}
269
270pub inline fn allocAdvancedWithRetAddr(
271 self: Allocator,
272 comptime T: type,
273 /// null means naturally aligned
274 comptime alignment: ?Alignment,
275 n: usize,
276 return_address: usize,
277) Error![]align(if (alignment) |a| a.toByteUnits() else @alignOf(T)) T {
278 const a: Alignment = alignment orelse comptime .of(T);
279 const ptr: [*]align(a.toByteUnits()) T = @ptrCast(try self.allocWithSizeAndAlignment(@sizeOf(T), a, n, return_address));
280 return ptr[0..n];
281}
282
283fn allocWithSizeAndAlignment(
284 self: Allocator,
285 comptime size: usize,
286 comptime alignment: Alignment,
287 n: usize,
288 return_address: usize,
289) Error![*]align(alignment.toByteUnits()) u8 {
290 const byte_count = math.mul(usize, size, n) catch return error.OutOfMemory;
291 return self.allocBytesAligned(alignment, byte_count, return_address);
292}
293
294pub fn allocBytesAligned(
295 self: Allocator,
296 comptime alignment: Alignment,
297 byte_count: usize,
298 return_address: usize,
299) Error![*]align(alignment.toByteUnits()) u8 {
300 if (byte_count == 0) {
301 const ptr = comptime alignment.backward(math.maxInt(usize));
302 return @as([*]align(alignment.toByteUnits()) u8, @ptrFromInt(ptr));
303 }
304
305 const byte_ptr = self.rawAlloc(byte_count, alignment, return_address) orelse return error.OutOfMemory;
306 @memset(byte_ptr[0..byte_count], undefined);
307 return @alignCast(byte_ptr);
308}
309
310/// Request to modify the size of an allocation.
311///
312/// It is guaranteed to not move the pointer, however the allocator
313/// implementation may refuse the resize request by returning `false`.
314///
315/// `allocation` may be an empty slice, in which case `false` is returned,
316/// unless `new_len` is also 0, in which case `true` is returned.
317///
318/// `new_len` may be zero, in which case the allocation is freed.
319pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool {
320 const slice_info = @typeInfo(@TypeOf(allocation)).pointer;
321 const T = if (slice_info.size != .slice) comptime T: {
322 assert(slice_info.size == .one);
323 break :T @typeInfo(slice_info.child).array.child;
324 } else slice_info.child;
325 if (new_len == 0) {
326 self.free(allocation);
327 return true;
328 }
329 if (allocation.len == 0) {
330 return false;
331 }
332 const old_memory: []u8 = @ptrCast(@constCast(mem.absorbSentinel(allocation)));
333 const new_len_bytes = math.mul(usize, @sizeOf(T), new_len) catch return false;
334 return self.rawResize(
335 old_memory,
336 .fromByteUnits(slice_info.attrs.@"align" orelse @alignOf(T)),
337 new_len_bytes,
338 @returnAddress(),
339 );
340}
341
342/// Request to modify the size of an allocation, allowing relocation.
343///
344/// A non-`null` return value indicates the resize was successful. The
345/// allocation may have same address, or may have been relocated. In either
346/// case, the allocation now has size of `new_len`. A `null` return value
347/// indicates that the resize would be equivalent to allocating new memory,
348/// copying the bytes from the old memory, and then freeing the old memory.
349/// In such case, it is more efficient for the caller to perform those
350/// operations.
351///
352/// `allocation` may be an empty slice, in which case `null` is returned,
353/// unless `new_len` is also 0, in which case `allocation` is returned.
354///
355/// `new_len` may be zero, in which case the allocation is freed.
356///
357/// If the allocation's elements' type is zero bytes sized, `allocation.len` is set to `new_len`.
358pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?Slice(AbsorbSentinel(@TypeOf(allocation))) {
359 const slice_info = @typeInfo(@TypeOf(allocation)).pointer;
360 const T = if (slice_info.size != .slice) comptime T: {
361 assert(slice_info.size == .one);
362 break :T @typeInfo(slice_info.child).array.child;
363 } else slice_info.child;
364
365 if (new_len == 0) {
366 self.free(allocation);
367 return allocation[0..0];
368 }
369 if (allocation.len == 0) {
370 return null;
371 }
372 if (@sizeOf(T) == 0) {
373 var new_memory = allocation;
374 new_memory.len = new_len;
375 return new_memory;
376 }
377 const old_memory: []u8 = @ptrCast(@constCast(mem.absorbSentinel(allocation)));
378 const new_len_bytes = math.mul(usize, @sizeOf(T), new_len) catch return null;
379 const new_ptr = self.rawRemap(
380 old_memory,
381 .fromByteUnits(slice_info.attrs.@"align" orelse @alignOf(T)),
382 new_len_bytes,
383 @returnAddress(),
384 ) orelse return null;
385 return @ptrCast(@alignCast(new_ptr[0..new_len_bytes]));
386}
387
388/// This function requests a new size for an existing allocation, which
389/// can be larger, smaller, or the same size as the old memory allocation.
390/// The result is an array of `new_n` items of the same type as the existing
391/// allocation.
392///
393/// If `new_n` is 0, this is the same as `free` and it always succeeds.
394///
395/// `old_mem` may have length zero, which makes a new allocation.
396///
397/// This function only fails on out-of-memory conditions, unlike:
398/// * `remap` which returns `null` when the `Allocator` implementation cannot
399/// do the realloc more efficiently than the caller
400/// * `resize` which returns `false` when the `Allocator` implementation cannot
401/// change the size without relocating the allocation.
402pub fn realloc(self: Allocator, old_mem: anytype, new_n: usize) Error!Slice(AbsorbSentinel(@TypeOf(old_mem))) {
403 return self.reallocAdvanced(old_mem, new_n, @returnAddress());
404}
405
406pub fn reallocAdvanced(
407 self: Allocator,
408 old_mem: anytype,
409 new_n: usize,
410 return_address: usize,
411) Error!Slice(AbsorbSentinel(@TypeOf(old_mem))) {
412 const slice_info = @typeInfo(@TypeOf(old_mem)).pointer;
413 const T = if (slice_info.size != .slice) comptime T: {
414 assert(slice_info.size == .one);
415 break :T @typeInfo(slice_info.child).array.child;
416 } else slice_info.child;
417 if (old_mem.len == 0) {
418 return self.allocAdvancedWithRetAddr(T, .fromByteUnitsOptional(slice_info.attrs.@"align"), new_n, return_address);
419 }
420 if (new_n == 0) {
421 self.free(old_mem);
422 const alignment = slice_info.attrs.@"align" orelse @alignOf(T);
423 const addr = comptime std.mem.alignBackward(usize, math.maxInt(usize), alignment);
424 const ptr: *align(alignment) [0]T = @ptrFromInt(addr);
425 return ptr;
426 }
427
428 const old_byte_slice: []u8 = @ptrCast(@constCast(mem.absorbSentinel(old_mem)));
429 const byte_count = math.mul(usize, @sizeOf(T), new_n) catch return error.OutOfMemory;
430 // Note: can't set shrunk memory to undefined as memory shouldn't be modified on realloc failure
431 if (self.rawRemap(old_byte_slice, .fromByteUnits(slice_info.attrs.@"align" orelse @alignOf(T)), byte_count, return_address)) |p| {
432 return @ptrCast(@alignCast(p[0..byte_count]));
433 }
434
435 const new_mem = self.rawAlloc(byte_count, .fromByteUnits(slice_info.attrs.@"align" orelse @alignOf(T)), return_address) orelse
436 return error.OutOfMemory;
437 const copy_len = @min(byte_count, old_byte_slice.len);
438 @memcpy(new_mem[0..copy_len], old_byte_slice[0..copy_len]);
439 @memset(old_byte_slice, undefined);
440 self.rawFree(old_byte_slice, .fromByteUnits(slice_info.attrs.@"align" orelse @alignOf(T)), return_address);
441
442 return @ptrCast(@alignCast(new_mem[0..byte_count]));
443}
444
445/// Free an array allocated with `alloc`.
446/// If memory has length 0, free is a no-op.
447/// To free a single item, see `destroy`.
448pub fn free(self: Allocator, memory: anytype) void {
449 const slice_info = @typeInfo(@TypeOf(memory)).pointer;
450 if (slice_info.size != .slice) {
451 comptime assert(slice_info.size == .one and @typeInfo(slice_info.child) == .array);
452 }
453 const bytes: []u8 = @ptrCast(@constCast(mem.absorbSentinel(memory)));
454 if (bytes.len == 0) return;
455 @memset(bytes, undefined);
456 self.rawFree(bytes, .fromByteUnits(slice_info.attrs.@"align" orelse @alignOf(slice_info.child)), @returnAddress());
457}
458
459/// Copies `m` to newly allocated memory. Caller owns the memory.
460pub fn dupe(allocator: Allocator, comptime T: type, m: []const T) Error![]T {
461 const new_buf = try allocator.alloc(T, m.len);
462 @memcpy(new_buf, m);
463 return new_buf;
464}
465
466/// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory.
467pub fn dupeSentinel(
468 allocator: Allocator,
469 comptime T: type,
470 m: []const T,
471 comptime sentinel: T,
472) Error![:sentinel]T {
473 const new_buf = try allocator.alloc(T, m.len + 1);
474 @memcpy(new_buf[0..m.len], m);
475 new_buf[m.len] = sentinel;
476 return new_buf[0..m.len :sentinel];
477}
478
479/// Allocates a formatted string which is returned on success.
480///
481/// Returned slice can be deallocated with `free`. If an arena-style allocator
482/// is used instead, such as `std.heap.ArenaAllocator`, then no call to `free`
483/// is necessary.
484///
485/// See `std.Io.Writer.print`.
486pub fn print(a: Allocator, comptime format: []const u8, args: anytype) Error![]u8 {
487 var aw = try std.Io.Writer.Allocating.initCapacity(a, format.len);
488 defer aw.deinit();
489 aw.writer.print(format, args) catch |err| switch (err) {
490 error.WriteFailed => return error.OutOfMemory,
491 };
492 return aw.toOwnedSlice();
493}
494
495test print {
496 const x: i32 = -1;
497 const y: []const u8 = "hi";
498 const a = std.testing.allocator;
499 const s = try print(a, "{d}={s}", .{ x, y });
500 defer free(a, s);
501 try std.testing.expectEqualStrings("-1=hi", s);
502}
503
504/// Like `print` but returned slice has the provided sentinel.
505///
506/// Returned slice can be deallocated with `free`. If an arena-style allocator
507/// is used instead, such as `std.heap.ArenaAllocator`, then no call to `free`
508/// is necessary. Illegal behavior occurs if the returned slice is type-coerced
509/// to a slice without the sentinel and then passed to `free`.
510pub fn printSentinel(
511 a: Allocator,
512 comptime format: []const u8,
513 args: anytype,
514 comptime sentinel: u8,
515) Allocator.Error![:sentinel]u8 {
516 var aw = try std.Io.Writer.Allocating.initCapacity(a, format.len);
517 defer aw.deinit();
518 aw.writer.print(format, args) catch |err| switch (err) {
519 error.WriteFailed => return error.OutOfMemory,
520 };
521 return aw.toOwnedSliceSentinel(sentinel);
522}
523
524test printSentinel {
525 const x: i32 = -1;
526 const y: []const u8 = "hi";
527 const a = std.testing.allocator;
528 const s = try printSentinel(a, "{d}={s}", .{ x, y }, 0);
529 defer free(a, s);
530 try std.testing.expectEqualStrings("-1=hi", s);
531 try std.testing.expectEqual(0, s[s.len]);
532}
533
534/// An allocator that always fails to allocate.
535pub const failing: Allocator = .{
536 .ptr = undefined,
537 .vtable = &.{
538 .alloc = noAlloc,
539 .resize = unreachableResize,
540 .remap = unreachableRemap,
541 .free = unreachableFree,
542 },
543};
544
545fn unreachableResize(
546 self: *anyopaque,
547 memory: []u8,
548 alignment: Alignment,
549 new_len: usize,
550 ret_addr: usize,
551) bool {
552 _ = self;
553 _ = memory;
554 _ = alignment;
555 _ = new_len;
556 _ = ret_addr;
557 unreachable;
558}
559
560fn unreachableRemap(
561 self: *anyopaque,
562 memory: []u8,
563 alignment: Alignment,
564 new_len: usize,
565 ret_addr: usize,
566) ?[*]u8 {
567 _ = self;
568 _ = memory;
569 _ = alignment;
570 _ = new_len;
571 _ = ret_addr;
572 unreachable;
573}
574
575fn unreachableFree(
576 self: *anyopaque,
577 memory: []u8,
578 alignment: Alignment,
579 ret_addr: usize,
580) void {
581 _ = self;
582 _ = memory;
583 _ = alignment;
584 _ = ret_addr;
585 unreachable;
586}
587
588test failing {
589 const f: Allocator = .failing;
590 try std.testing.expectError(error.OutOfMemory, f.alloc(u8, 123));
591 // Expect very large allocations to fail at the implementation level and not in the interface
592 try std.testing.expectError(error.OutOfMemory, f.alloc(u8, std.math.maxInt(usize)));
593 try std.testing.expectError(error.OutOfMemory, f.allocSentinel(u8, std.math.maxInt(usize) - 1, 0));
594}
595
596test "free single-pointer to array" {
597 const allocator = std.testing.allocator;
598 {
599 const allocation = try allocator.alloc(u32, 128);
600 allocation[127] = 0;
601 const ptr: *[127:0]u32 = allocation[0..127 :0];
602 allocator.free(ptr);
603 }
604 {
605 const allocation = try allocator.alloc(u32, 128);
606 allocation[127] = 0;
607 const ptr: *[127:0]u32 = allocation[0..127 :0];
608 if (allocator.resize(ptr, 16)) {
609 allocator.free(ptr[0..16]);
610 } else allocator.free(ptr);
611 }
612 {
613 const allocation = try allocator.alloc(u32, 128);
614 allocation[127] = 0;
615 const ptr: *[127:0]u32 = allocation[0..127 :0];
616 if (allocator.remap(ptr, 16)) |new| {
617 allocator.free(new);
618 } else allocator.free(ptr);
619 }
620 {
621 const allocation = try allocator.alloc(u32, 128);
622 allocation[127] = 0;
623 const ptr: *[127:0]u32 = allocation[0..127 :0];
624 if (allocator.realloc(ptr, 16)) |new| {
625 allocator.free(new);
626 } else |_| {
627 allocator.free(allocation);
628 }
629 }
630}