A thread-safe object pool which will dynamically grow when empty and revert to the configured size.

// create a pool for our Expensive class.
// Our Expensive class takes a special initializing context, here an usize which
// we set to 10_000. This is just to pass data from the pool into Expensive.init
var pool = try zul.pool.Growing(Expensive, usize).init(allocator, 10_000, .{.count = 100});
defer pool.deinit();

// acquire will either pick an item from the pool
// if the pool is empty, it'll create a new one (hence, "Growing")
var exp1 = try pool.acquire();
defer pool.release(exp1);

...

// pooled object must have 3 functions
const Expensive = struct {
	// an init function
	pub fn init(allocator: Allocator, size: usize) !Expensive {
		return .{
			// ...
		};
	}

	// a deinit method
	pub fn deinit(self: *Expensive) void {
		// ...
	}

	// a reset method, called when the item is released back to the pool
	pub fn reset(self: *Expensive) void {
		// ...
	}
};

fn Growing(comptime T: type, comptime C: type) Growing(T, C)

The Growing pool is a generic function that takes two parameters. T is the type of object being pool. C is the type of data to pass into T.init. In many cases, C will be void, in which case T.init will not receive the value:

var pool = try zul.pool.Growing(Expensive, void).init(allocator, {}, .{.count = 100});
defer pool.deinit();

...

const Expensive = struct {
	// Because the context was defined as `void`, the `init` function does not
	// take a 2nd paremeter.
	pub fn init(allocator: Allocator) !Expensive {
		return .{
			// ...
		};
	}

	// ...
};

T must have an init(allocator: Allocator, ctx: C) !T function. It must also have the following two methods: deinit(self: *T) void and reset(self: *T) void. Because the pool will dynamically create T when empty, deinit will be called when items are released back into a full pool (as well as when pool.deinit is called). reset is called whenever an item is released back into the pool.

fn init(
	// Allocator is used to create the pool, create the pooled items, and is passed
	// to the T.init
	allocator: std.mem.Allocator,

	// An arbitrary context to passed to T.init
	ctx: C

	opts: .{
		// number of items to keep in the pool
		.count: usize,
	}
) !Growing(T, C)

Creates a pool.Growing.

This is method thread-safe.

Returns an *T. When available, *T will be retrieved from the pooled objects. When the pool is empty, a new *T is created.

Releases *T back into the pool. If the pool is full, t.deinit() is called and then discarded.