Efficiently create/concat strings or binary data, optionally using a thread-safe pool with pre-allocated static buffers.
Applications that need to create many StringBuilders
should consider using a zul.StringBuilder.Pool
. StringBuilders
created via the pool have a re-usable static buffer which can eliminate or reduce the need for dynamic memory allocations (assume the size of the static buffer is larger than the typical data written).
var sb = zul.StringBuilder.init(allocator);
defer sb.deinit();
var view = try sb.skip(4);
try sb.writeByte(10);
try sb.write("hello");
view.writeU32Big(@intCast(sb.len() - 4));
std.debug.print("{any}\n", .{sb.string()});
zul.StringBuilder
is a wrapper around a dynamically growable []u8
and is designed for string concatenation or creating binary payloads. While std.ArrayList(u8)
can be used for the same purpose, the non-generic nature of StringBuilder
results in a cleaner interface.
endian: std.builtin.Endian
The endianness to use, .big
or .little
, when encoding integers. Defaults to the current architecture.
fromOwnedSlice(...) StringBuilder
fn fromOwnedSlice(
allocator: std.mem.Allocator,
buf: []u8,
) StringBuilder
Creates a StringBuilder around the provided buf
. buf
must have been created by the provided allocator
.
fromReader(...) !StringBuilder
fn fromReader(
allocator: std.mem.Allocator,
reader: anytype,
opts: FromReaderOpts
) !StringBuilder
Creates and populates a StringBuilder
by reading from the reader
.
Options:
max_size: usize
- Will return an error.TooBig
if more than the specified value is read. Default: std.math.maxInt(usize)
.
buffer_size: usize
- For the sake of efficiency, the reader is read directly into the StringBuilder's underlying buffer (there is no intermediate buffer). `buffer_size` controls the (a) initial size and (b) growth of the underlying buffer. Defaults to `8192`. Is forced to 64
when the value is less than 64
release(self: *StringBuilder) void
Releases the StringBuilder
back to the pool. This method is only valid when the StringBuilder
was retrieved from pool.acquire()
. The StringBuilder
should not be used after this is called.
skip(sb: *StringBuilder, count: usize) !View
Skips the specified number of bytes and returns a View
that can be used to backfill the skipped data.
count
can be larger than the existing buffer length, in which case skip
will attempt to grow the buffer.
The StringBuilder
exposes a variety of methods for writing integers. There's overlap between some of these functions in order to accommodate various styles/preferences.
When the sb.skip()
method is called, a View
is returned to backfill the skipped space. This is API exists to "reserve" space within the buffer for data unknown until a later point. The typical example is a length-prefixed message where the length is only known after the message has been written.
The View
exposes all of the same write*
methods as the StringBuilder
, but will not grow the buffer and does no bound-checking.
zul.StringBuilder.Pool
is a thread-safe pool of zul.StringBuilders
that will grow as needed. Each zul.StringBuilder
has a re-usable static buffer which will be used before any dynamic allocations are needed.
var pool = zul.StringBuilder.Pool.init(t.allocator, 10, 1024);
defer pool.deinit();
var sb = try pool.acquire();
defer sb.release();
sb.write("hello world");
...
deinit(self: *Pool) void
Releases all allocated memory. The Pool
and any of its StringBuilders
should not be used after this is called.
release(self: *Pool, sb: *StringBuilder) void
Releases the StringBuilder
back into the pool. If the pool is full, the StringBuilder
is freed. This method is thread-safe. Most implentations will prefer to call release()
on the StringBuilder
directly.