Hello Everyone,
I’m new to Zig and coming from higher‑level languages. To learn Zig, especially its memory management and idioms, I wrote a (buggy) gap buffer implementation. The code is below. I’d appreciate any feedback on Zig idioms, memory management, performance, algorithm correctness, and error handling, since that’s an area I’m still adjusting to after working with higher‑level languages.
Cheers.
const std = @import(“std”);
const Allocator = std.mem.Allocator;
const print = std.debug.print;
const GapBuffer = struct {
allocator: Allocator,
data: u8,
gap_start: usize = 0,
gap_end: usize,
capacity: usize,
pub fn init(allocator: Allocator, capacity: usize) !GapBuffer {
return .{
.allocator = allocator,
.data = try allocator.alloc(u8, capacity),
.gap_end = capacity - 1,
.capacity = capacity,
};
}
pub fn deinit(self: *GapBuffer) void {
self.allocator.free(self.data);
}
pub fn insert(self: *GapBuffer, char: u8) !void {
if (self.gap_end == self.gap_start) {
try self.grow();
}
self.data[self.gap_start] = char;
self.gap_start += 1;
}
pub fn delete(self: *GapBuffer) !void {
if (self.gap_end == self.data.len - 1) {
return;
}
self.data[self.gap_end + 1] = undefined;
self.gap_end += 1;
}
pub fn backspace(self: *GapBuffer) !void {
if (self.gap_start == 0) {
return;
}
self.data[self.gap_start - 1] = undefined;
self.gap_start -= 1;
}
pub fn move_cursor_forward(self: *GapBuffer) !void {
if (self.gap_start == self.data.len - 1) {
return;
}
if (self.gap_end < self.gap_start) {
try self.grow();
}
const char = self.data[self.gap_start + 1];
self.data[self.gap_start + 1] = self.data[self.gap_end];
self.data[self.gap_end] = char;
self.gap_end += 1;
self.gap_start += 1;
}
pub fn move_cursor_backward(self: *GapBuffer) !void {
if (self.gap_start == 0) {
return;
}
if (self.gap_end < self.gap_start) {
try self.grow();
}
const char = self.data[self.gap_start - 1];
self.data[self.gap_start - 1] = self.data[self.gap_end];
self.data[self.gap_end] = char;
self.gap_end -= 1;
self.gap_start -= 1;
}
pub fn grow(self: *GapBuffer) !void {
// alloc new capacity
const new_data = try self.allocator.alloc(u8, self.data.len + self.capacity);
// copy the left of the gap
// memory: [A][B][C][][X][Z], collect [A][B][C]
const left_data: []u8 = self.data[0..self.gap_start];
@memcpy(new_data[0..self.gap_start], left_data);
const right_data_start = self.gap_end + 1;
const right_data_end = self.data.len - 1;
var right_data: []u8 = &.{};
if (right_data_start <= right_data_end) {
right_data = self.data[right_data_start .. right_data_end + 1];
}
// memory: [A][B][C][][X][Z], collect [X][Z]
// copy right of the gap
const new_right_data_start = new_data.len - right_data.len;
@memcpy(
new_data[new_right_data_start..],
right_data,
);
self.data = new_data;
self.gap_end += self.capacity;
}
};
test "test gapp buffer with different operations" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
const capacity: usize = 3;
var gap_buffer = try GapBuffer.init(allocator, capacity);
const max = 3;
var insert_times: u8 = max;
for (0..insert_times) |i| {
try gap_buffer.insert(@intCast(97 + i));
}
print("INSERT {d} : {s}\n", .{ max, gap_buffer.data });
// data: abc¬¬¬
var left_shift: u8 = max - 1;
for (0..left_shift) |_| {
try gap_buffer.move_cursor_backward();
}
print("LEFT {d} : {s}\n", .{ left_shift, gap_buffer.data });
insert_times = max + 5;
for (0..insert_times) |i| {
try gap_buffer.insert(@intCast(65 + i));
}
print("INSERT {d} : {s}\n", .{ insert_times, gap_buffer.data });
left_shift = max - 1;
for (0..left_shift) |_| {
try gap_buffer.move_cursor_backward();
}
print("LEFT {d} : {s}\n", .{ left_shift, gap_buffer.data });
const right_shift = max;
for (0..right_shift) |_| {
try gap_buffer.move_cursor_forward();
}
print("RIGHT {d} : {s}\n", .{ right_shift, gap_buffer.data });
var delete_times: u8 = 1;
for (0..delete_times) |_| {
try gap_buffer.delete();
}
print("DELETE {d} : {s}\n", .{ delete_times, gap_buffer.data });
delete_times = 2;
for (0..delete_times) |_| {
try gap_buffer.backspace();
}
print("BACKSPACE {d} : {s}\n", .{ delete_times, gap_buffer.data });
left_shift = 3;
for (0..left_shift) |_| {
try gap_buffer.move_cursor_backward();
}
print("LEFT {d} : {s}\n", .{ left_shift, gap_buffer.data });
try gap_buffer.insert(' ');
try gap_buffer.insert('1');
print("INSERT {d} : {s}\n", .{ insert_times, gap_buffer.data });
}
apologies for lack of comments.