···3838```
39394040The `Pool` is thread-safe. The `StringBuilder` is not thread safe.
4141+4242+For a more advanced use case, `pool.acquireWithAllocator(std.mem.Allocator)` can be used. This has a specific purpose: to allocate the static buffer upfront using [probably] a general purpose allocator, but for any dynamic allocation to happen with a [probably] arena allocator. This is meant for the case where an arena allocator is available which outlives the checked out buffer. In such cases, using the arena allocator for any potential dynamic allocation by the buffer will offer better performance.
+21
src/pool.zig
···4141 }
42424343 pub fn acquire(self: *Pool) !*StringBuilder {
4444+ return self.acquireWithAllocator(self.allocator);
4545+ }
4646+4747+ pub fn acquireWithAllocator(self: *Pool, dyn_allocator: Allocator) !*StringBuilder {
4448 self.mutex.lock();
45494650 const builders = self.builders;
···5357 const sb = try allocator.create(StringBuilder);
5458 sb.* = try StringBuilder.init(allocator, self.builder_size);
5559 if (comptime builtin.is_test) sb.buf[0] = 0;
6060+ sb._da = dyn_allocator;
5661 return sb;
5762 }
5863 const index = available - 1;
5964 const sb = builders[index];
6065 self.available = index;
6166 self.mutex.unlock();
6767+ sb._da = dyn_allocator;
6268 return sb;
6369 }
6470···101107 p.release(sb3a);
102108 p.release(sb2a);
103109 p.release(sb1b);
110110+}
111111+112112+test "pool: dynamic allocator" {
113113+ var p = try Pool.init(t.allocator, 2, 5);
114114+ defer p.deinit();
115115+116116+ var arena = std.heap.ArenaAllocator.init(t.allocator);
117117+ defer arena.deinit();
118118+119119+ var sb = p.acquireWithAllocator(arena.allocator()) catch unreachable;
120120+ try sb.write("hello world how's it going?");
121121+ try sb.write("he");
122122+ try sb.write("hello world");
123123+ try sb.write("are you doing well? I hope so, I don't love how this is being implemented, but I think the feature is worthwhile");
124124+ p.release(sb);
104125}
105126106127test "pool: threadsafety" {
+20-6
src/string_builder.zig
···44pub const Pool = @import("pool.zig").Pool;
5566pub const StringBuilder = struct {
77- allocator: Allocator,
77+ // Two allocators! This is largely a feature meant to be used with the Pool.
88+ // Imagine you have a pool of 100 StringBuilders. Each one has a static buffer
99+ // of 2K, allocated with a general purpose allocator. We store that in _a.
1010+ // Now you acquire an SB and start to write. You write more than 2K, so we
1111+ // need to allocate `dynamic`. Yes, we could use our general purpose allocator
1212+ // (aka _a), but what if the app would like to use a different allocator for
1313+ // that, like an Arena?
1414+ // Thus, `static` is always allocated with _a, and apps can opt to use a
1515+ // different allocator, _da, to manage `dynamic`. `_da` is meant to be set
1616+ // via pool.acquireWithAllocator since we expect _da to be transient.
1717+ _a: Allocator,
1818+1919+ _da: ?Allocator,
820921 // where in buf we currently are
1022 pos: usize,
···2133 pub fn init(allocator: Allocator, size: usize) !StringBuilder {
2234 const static = try allocator.alloc(u8, size);
2335 return .{
3636+ ._a = allocator,
3737+ ._da = null,
2438 .pos = 0,
2539 .dynamic = null,
2640 .buf = static,
2741 .static = static,
2828- .allocator = allocator,
2942 };
3043 }
31443245 pub fn deinit(self: StringBuilder) void {
3333- const allocator = self.allocator;
4646+ const allocator = self._a;
3447 allocator.free(self.static);
3548 if (self.dynamic) |dyn| {
3636- allocator.free(dyn);
4949+ (self._da orelse allocator).free(dyn);
3750 }
3851 }
39524053 pub fn reset(self: *StringBuilder) void {
4154 self.pos = 0;
4255 if (self.dynamic) |dyn| {
4343- self.allocator.free(dyn);
5656+ (self._da orelse self._a).free(dyn);
4457 self.dynamic = null;
4558 self.buf = self.static;
4659 }
6060+ self._da = null;
4761 }
48624963 pub fn len(self: StringBuilder) usize {
···113127 if (new_capacity >= required_capacity) break;
114128 }
115129116116- const allocator = self.allocator;
130130+ const allocator = self._da orelse self._a;
117131 if (buf.ptr == self.static.ptr or !allocator.resize(buf, new_capacity)) {
118132 const new_buffer = try allocator.alloc(u8, new_capacity);
119133 std.mem.copyForwards(u8, new_buffer[0..buf.len], buf);