Hi, there is still the same issues with your code, It’s not very conventional obviously, you can write the code that you like and you don’t need to justify yourself, nothing wrong with that, but if you’d like to make more idiomatic code, code that fits what people expect from a Zig container, than it’s really not that hard, you just have to copy the ArrayList
interface, and then you can expand it and specialize it to your liking. I’ve made a quick example.
pub const DynamicString = struct {
items: []u8,
capacity: usize,
allocator: Allocator,
pub fn init(allocator: Allocator) DynamicString {
return .{
.items = &[_]u8{},
.capacity = 0,
.allocator = allocator,
};
}
pub fn initCapacity(allocator: Allocator, num: usize) Allocator.Error!DynamicString {
var self = init(allocator);
try self.ensureTotalCapacityPrecise(num);
return self;
}
pub fn deinit(self: *DynamicString) void {
self.allocator.free(self.allocatedSlice());
}
pub fn append(self: *DynamicString, item: u8) Allocator.Error!void {
const new_item_ptr: *u8 = try self.addOne();
new_item_ptr.* = item;
}
pub fn addOne(self: *DynamicString) Allocator.Error!*u8 {
const new_len = self.items.len + 1;
try self.ensureTotalCapacity(new_len);
return self.addOneAssumeCapacity();
}
pub fn ensureTotalCapacity(self: *DynamicString, new_capacity: usize) Allocator.Error!void {
if (self.capacity >= new_capacity) {
return;
}
const better_capacity = growCapacity(self.capacity, new_capacity);
return try self.ensureTotalCapacityPrecise(better_capacity);
}
pub fn addOneAssumeCapacity(self: *DynamicString) *u8 {
std.debug.assert(self.items.len < self.capacity);
self.items.len += 1;
return &self.items[self.items.len - 1];
}
pub fn ensureTotalCapacityPrecise(self: *DynamicString, new_capacity: usize) Allocator.Error!void {
if (self.capacity >= new_capacity) {
return;
}
const old_memory = self.allocatedSlice();
if (self.allocator.resize(old_memory, new_capacity)) {
self.capacity = new_capacity;
} else {
const new_memory = try self.allocator.alloc(u8, new_capacity);
@memcpy(new_memory[0..self.items.len], self.items);
self.allocator.free(old_memory);
self.items.ptr = new_memory.ptr;
self.capacity = new_memory.len;
}
}
fn growCapacity(current: usize, minimum: usize) usize {
var new = current;
while (true) {
new +|= new / 2 + 8;
if (new >= minimum)
return new;
}
}
pub fn allocatedSlice(self: *DynamicString) []u8 {
return self.items.ptr[0..self.capacity];
}
pub fn view(self: *DynamicString) []const u8 {
return self.items;
}
};
pub const DynamicStringUnmanaged = struct {
items: []u8,
capacity: usize,
pub fn init() DynamicStringUnmanaged {
return .{
.items = &[_]u8{},
.capacity = 0,
};
}
pub fn initCapacity(allocator: Allocator, num: usize) Allocator.Error!DynamicStringUnmanaged {
var self = init();
try self.ensureTotalCapacityPrecise(allocator, num);
return self;
}
pub fn deinit(self: *DynamicStringUnmanaged, allocator: Allocator) void {
allocator.free(self.allocatedSlice());
}
pub fn append(self: *DynamicStringUnmanaged, allocator: Allocator, item: u8) Allocator.Error!void {
const new_item_ptr: *u8 = try self.addOne(allocator);
new_item_ptr.* = item;
}
pub fn addOne(self: *DynamicStringUnmanaged, allocator: Allocator) Allocator.Error!*u8 {
const new_len = self.items.len + 1;
try self.ensureTotalCapacity(allocator, new_len);
return self.addOneAssumeCapacity();
}
fn growCapacity(current: usize, minimum: usize) usize {
var new = current;
while (true) {
new +|= new / 2 + 8;
if (new >= minimum)
return new;
}
}
pub fn ensureTotalCapacity(self: *DynamicStringUnmanaged, allocator: Allocator, new_capacity: usize) Allocator.Error!void {
if (self.capacity >= new_capacity) {
return;
}
const better_capacity = growCapacity(self.capacity, new_capacity);
return try self.ensureTotalCapacityPrecise(allocator, better_capacity);
}
pub fn addOneAssumeCapacity(self: *DynamicStringUnmanaged) *u8 {
std.debug.assert(self.items.len < self.capacity);
self.items.len += 1;
return &self.items[self.items.len - 1];
}
pub fn ensureTotalCapacityPrecise(self: *DynamicStringUnmanaged, allocator: Allocator, new_capacity: usize) Allocator.Error!void {
if (self.capacity >= new_capacity) {
return;
}
const old_memory = self.allocatedSlice();
if (allocator.resize(old_memory, new_capacity)) {
self.capacity = new_capacity;
} else {
const new_memory = try allocator.alloc(u8, new_capacity);
@memcpy(new_memory[0..self.items.len], self.items);
allocator.free(old_memory);
self.items.ptr = new_memory.ptr;
self.capacity = new_memory.len;
}
}
pub fn allocatedSlice(self: *DynamicStringUnmanaged) []u8 {
return self.items.ptr[0..self.capacity];
}
pub fn view(self: *DynamicStringUnmanaged) []const u8 {
return self.items;
}
};
and a main to test.
const std = @import("std");
const DynamicString = @import("root.zig").DynamicString;
const DynamicStringUnmanaged = @import("root.zig").DynamicStringUnmanaged;
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer {
if (gpa.detectLeaks()) {
@panic("leak detected");
}
_ = gpa.deinit();
}
const allocator = gpa.allocator();
{
var str = try DynamicString.initCapacity(allocator, 8);
defer str.deinit();
const my_text = "Hello, World!";
for (my_text) |item| {
try str.append(item);
}
std.debug.print("{s}", .{str.view()});
}
{
var str = try DynamicStringUnmanaged.initCapacity(allocator, 8);
defer str.deinit(allocator);
const my_text = "Hello, World!";
for (my_text) |item| {
try str.append(allocator, item);
}
std.debug.print("{s}", .{str.view()});
}
}
Obviously my code isn’t perfect either but at least it’s quite conventional, and I don’t think it would shock anybody. But to give you more explanation about why this code is more idiomatic.
My code unlike yours doesn’t commit to one type of allocator, it allows the user to provide whatever is more convenient, it offers one version that needs to be passed an explicit allocator, and one that stores it during the call to init. The code doesn’t use a custom byte type, just a u8, it doesn’t offer len/size function like in C++ because those properties can just be accessed directly. The naming convention is also a 1:1 mapping to the one in ArrayList.
Anyway which you the best