Unsure about how to handle function parameters

Hey everyone,

I’m refactoring the API for my S3 client lib (zig-s3) and want to see what people prefer regarding ergonomics and Zig conventions.

Initially, functions used plain positional arguments. As parameters started piling up, I considered switching everything to struct literals, but wrapping mandatory parameters in a struct feels like unnecessary friction for simple calls.

Comparing both approaches:

Everything in a struct

try client.createBucket(.{ .bucket_name = "my-bucket" });

var uploader = client.uploader(.{});
try uploader.uploadString(.{
    .bucket_name = "my-bucket",
    .key = "hello.txt",
    .content = "Hello, S3!",
});

const objects = try client.listObjects(.{
    .bucket_name = "my-bucket",
    .prefix = "folder1/",
});

Positional for required args and trailing struct for options

try client.createBucket("my-bucket");

var uploader = client.uploader(.{});
try uploader.uploadString("my-bucket", "hello.txt", "Hello, S3!");

const objects = try client.listObjects("my-bucket", .{ .prefix = "folder1/" });

What do you think? How do you usually handle this in your libraries?

Usually used like this.

“Everything in a struct” is somewhat ergonomic, but it’s not great for optimization when it comes to parameter passing. Also, this kind of ‘ergonomics’ can be covered by the IDE when reading, but from a writing perspective, it adds repetition of parameter names.

A ‘trailing struct for options’ is a pretty decent compromise.