How to handle args count on Windows

On Mac and Linux, this works completely fine:

pub fn main(init: std.process.Init.Minimal) !void {
    // Mem allocator init
    var arena = std.heap.ArenaAllocator.init(std.heap.smp_allocator);
    defer arena.deinit();
    const alloc = arena.allocator();

    // stdin
    var args = try init.args.iterateAllocator(alloc);
    defer args.deinit();

    // Handle in main, avoids unnecessary inits
    const name = args.next().?;
    // Handle empty args case early
    if (args.inner.remaining.len == 0) {
        std.log.err("Usage: {s} [options] <src...> <dst>", .{name});
        return error.InvalidArgs;
    }
....
}

Is there a more generic way of getting overall index count that I’m missing?

you can use arg.toSlice(arena) to get a slice of strings.

But I don’t think you need that, you know if there are 0 args when the first next (ignoring skipping the exe name) returns null

I wanted to check if there is anything after the first arg, to avoid doing all initing after, and continued parsing of the args. My code currently looks like this:

pub fn main(init: std.process.Init.Minimal) !void {
    // Mem allocator init
    var arena = std.heap.ArenaAllocator.init(std.heap.smp_allocator);
    defer arena.deinit();
    const alloc = arena.allocator();

    // stdin
    var args = try init.args.iterateAllocator(alloc);
    defer args.deinit();

    // Handle in main, avoids unnecessary inits
    const name = args.next().?;
    // Handle empty args case early
    if (args.inner.remaining.len == 0) {
        std.log.err("Usage: {s} [options] <src...> <dst>", .{name});
        return error.InvalidArgs;
    }

    // Io init
    var io_init = std.Io.Threaded.init(
        alloc,
        .{ .environ = init.environ },
    );
    const io = io_init.io();

    // sdout
    var stdout_init = std.Io.File.stdout().writer(io, &.{});
    const stdout = &stdout_init.interface;

    var cfg = try Config.parse(alloc, &args, stdout, name);
    defer cfg.deinit(alloc);
...
// config.zig:
const std = @import("std");
const Allocator = std.mem.Allocator;
const Iterator = std.process.Args.Iterator;
const Sort = @import("root.zig").Sort;

const help_msg =
    \\Usage: {s} [options] <src...> <dest>
    \\
    \\Options:
    \\  -a, --acl        Copy ACLs/xattrs
    \\  -b, --binary     Use binary, lexicographic sorting
    \\  -p, --path       Copy path passed as argument (no flatten)
    \\  -v, --verbose    Sets logging level to debug
    \\  -h, --help       Show this help message
    \\
;

pub const Config = struct {
    const Self = @This();

    acls: bool = false,
    flatten: bool = true,
    mkdir: bool = false,
    falloc_switch: bool = true,
    sort: Sort = .natural,
    log_level: std.log.Level = std.log.default_level,
    src_list: std.ArrayList([]const u8) = .empty,
    dest: []const u8 = "",

    pub fn parse(alloc: Allocator, args: *Iterator, writer: *std.Io.Writer, name: []const u8) !Self {
        var self = Self{};

        // Only on error
        errdefer self.deinit(alloc);

        while (args.next()) |arg| {
            if (std.mem.eql(u8, arg, "-h") or std.mem.eql(u8, arg, "--help")) {
                try writer.print(help_msg, .{name});
                try writer.flush();
                self.deinit(alloc);
                std.process.exit(0);
            } else if (std.mem.eql(u8, arg, "-a") or std.mem.eql(u8, arg, "--acl")) {
                self.acls = true;
            } else if (std.mem.eql(u8, arg, "-v") or std.mem.eql(u8, arg, "--verbose")) {
                self.log_level = .debug;
            } else if (std.mem.eql(u8, arg, "-p") or std.mem.eql(u8, arg, "--path")) {
                self.flatten = false;
            } else if (std.mem.eql(u8, arg, "-b") or std.mem.eql(u8, arg, "--binary")) {
                self.sort = .binary;
            } else {
                try self.src_list.append(alloc, arg);
            }
        }

        if (self.src_list.items.len < 2) {
            std.log.err("Usage: {s} [options] <src...> <dst>", .{name});
            return error.InvalidArgs;
        }

        self.dest = self.src_list.pop().?;
        return self;
    }

    pub fn deinit(self: *Self, alloc: Allocator) void {
        self.src_list.deinit(alloc);
        self.* = undefined;
    }
};

I had in mind “Exit early as possible if arg count is not valid”. I’ll try doing something with toSlice()

You still dont need to know the len, or use toSlice. You would just have to deal with already having the first arg which is not hard:

var next_arg: ?[:0]const u8 = args.next() orelse {
    std.log.err("Usage: {s} [options] <src...> <dst>", .{name});
    return error.InvalidArgs;
};
...
while (next_arg) |arg| : (next_arg = args.next()) {
...
}

Its mostly preference, I doubt arg parsing is a performance/efficiency concern

Ah, that’s a nice way to put it. I did not think of using while with orelse. Thanks for that. Instead I went digging in the std and started writing comptime switch:

    switch (builtin.os.tag) {
        .windows => {
            if (args.inner.buffer.len == 0) {
                std.log.err("Usage: {s} [options] <src...> <dst>", .{name});
                return error.InvalidArgs;
            }
        },
        .wasi => {
            if (builtin.link_libc) {
                if (args.inner.remaining.len == 0) {
                    std.log.err("Usage: {s} [options] <src...> <dst>", .{name});
                    return error.InvalidArgs;
                }
            } else {
                if (args.inner.args.len == 0) {
                    std.log.err("Usage: {s} [options] <src...> <dst>", .{name});
                    return error.InvalidArgs;
                }
            }
        },
        else => {
            if (args.inner.remaining.len == 0) {
                std.log.err("Usage: {s} [options] <src...> <dst>", .{name});
                return error.InvalidArgs;
            }
        },
    }

It’s not a performance issue, it’s just if I see a possible performance gain, I like to try and implement it :sweat_smile:
From what I’ve seen the tool doing, main issues are cache misses and branching