General string handling

I find myself constantly struggling with strings in any form.
A simple example. How to code open_nextfile() neatly.

const source_dir = "C:/Data/Tmp/";

fn open_nextfile(filename: []const u8) void {
    openfile(...); // source_dir + filename
}

fn openfile(full_filename: []const u8) {
    // open file with absolute filename
}

Three options

  • std.mem.concat or std.mem.join (they are different)
  • std.fs.path.join
  • open source dir, then open file relative to it.
3 Likes

Ok. That’s good advice. Thanks.

And in general (no filenames) we can only use buffers of some kind or an oldstyle BoundedArray I guess?
In the case of std.mem.concat() we manage (free) the buffer after use.

(used to programming with managed strings for many years… still an inconvenient experience with zig).

ArrayList is also an option.

Otherwise, you could make your own type that is more convenient

If the string is short lived and you have the stack space, you can also just quickly use a stack buffer with std.fs.max_path_bytes std.Io.Dir.max_path_bytes and then bufPrint into that.

1 Like

That is true, but it also has a bunch of (comptime) format machinery.

Alternatively you could use either of the std.mem functions i mentioned with a fixed buffer allocator, though that may have more runtime cost and bloat.

Pros and cons.

1 Like

It’s recommended to reuse handles more and reduce the frequency of path parsing when dealing with file operations. Hard-coded absolute directory paths generally aren’t very likely to appear in programming practice either. Generally, it goes like this:
(Assume the use case here: write file; create if the target doesn’t exist)

const file_name = "1.txt";
const dir_permissions: std.Io.File.Permissions = blk: {
    if (std.posix.mode_t != u0) break :blk .fromMode(0o755) else break :blk .default_dir;
};
const source_dir: std.Io.Dir = try .createDirPathOpen(.cwd(), io, source_dir_path, .{ .permissions = dir_permissions });
defer source_dir.close(io);
const file_permissions: std.Io.File.Permissions = blk: {
    if (std.posix.mode_t != u0) break :blk .fromMode(0o644) else break :blk .default_file;
};
const next_file = try source_dir.createFile(io, file_name, .{ .truncate = false, .permissions = file_permissions });
defer next_file.close(io);

If there’s a strong reason to handle files based on parsed paths, a simple solution is just to use allocPrint (note that in the latest master branch, it’s been moved to a method of the allocator). The caller can decide whether to implement it based on allocation, arena, or a fixed buffer by passing in different allocators.

fn openNextfile(
    source_dir_path: []const u8,
    file_name: []const u8,
    options: struct {
        mode: std.Io.Dir.OpenFileOptions.Mode,
        lock: std.Io.File.Lock = .none,
        lock_nonblocking: bool = false,
        resolve_beneath: bool = false,
    },
    io: std.Io,
    scratch_allocator: std.mem.Allocator,
) (std.mem.Allocator.Error || std.Io.File.OpenError)!std.Io.File {
    // master branch:
    // const full_file_path = try scratch_allocator.print("{s}/{s}", .{ source_dir_path, file_name });
    const full_file_path = try std.fmt.allocPrint(scratch_allocator, "{s}/{s}", .{ source_dir_path, file_name });
    defer scratch_allocator.free(full_file_path);
    return switch (options.mode) {
        .read_only => try std.Io.Dir.cwd().openFile(io, full_file_path, .{
            .mode = .read_only,
            .allow_directory = false,
            .path_only = false,
            .lock = options.lock,
            .lock_nonblocking = options.lock_nonblocking,
            .allow_ctty = false,
            .follow_symlinks = true,
            .resolve_beneath = options.resolve_beneath,
        }),
        .write_only, .read_write => try std.Io.Dir.cwd().createFile(io, full_file_path, .{
            .read = options.mode != .write_only,
            .truncate = false,
            .exclusive = false,
            .lock = options.lock,
            .lock_nonblocking = options.lock_nonblocking,
            .permissions = blk: {
                if (std.posix.mode_t != u0) break :blk .fromMode(0o644) else break :blk .default_file;
            },
            .resolve_beneath = options.resolve_beneath,
        }),
    };
}

1 Like

Thanks! I did not know before this thread that we can “open” a directory.
So I learned about files opening and that allocPrint can be useful as well.