How to use root.zig effectively? (How to expose a good external Zig API?)

I currently have a project like this.

myproj$ tree .
.
├── build.zig
├── build.zig.zon
├── src
│   ├── foo.zig
│   ├── bar.zig
└── └── root.zig

And my foo.zig has a function like this.

// myproj/foo.zig
pub fn print(msg: []const u8) void {}

Within the same project, the name print seems fine. For example in bar.zig, I call it like this.

// myproj/bar.zig
const foo = @import("foo.zig");

fn bar() void {
  // It's a foo-printer, makes sense.
  foo.print("hi");
}

The part I’m not sure about is this: how should I organize my module so things read OK outside of my package? How should I write my root.zig file?

// myproj/root.zig

const foo = @import("foo.zig");

// Option A: We've lost the context of "foo". "myproj.print" is less clear.
pub const print = foo.print;

// Option B: Add "foo" context back in. "myproj.fooPrint" makes more sense...
// Although is this confusing now?  Is a user going to look for a function
// named "fooPrint" and not find it? Creating different names for the same
// thing feels weird...
pub const fooPrint = foo.print;
// Some other name
pub const logPrinter = foo.print;

// Option C: Just thought of this one... while writing this post...
// "myproj.foo.print" seems fine. Should this be the default way of
// re-exporting?
pub const foo = foo;

To add to the confusion, it seems like the generated docs only partially take the root.zig file into account? For example, I guess it lists the stuff in root.zig on the index page, but it totally exposes the internal files anway. So to see docs for myproj.fooPrint, users have to navigate to the foo file, then print.

Which leads me to another question, are pub declarations in root.zig additional exports available to the user of the library? Meaning, regardless of root.zig, can users still access the internal files, foo.zig, bar.zig, etc?

just to answer this question: no. pub is pub. try it yourself and see. const proj = @import("myproject") is effectively the same as const proj = @import("myproj/root.zig").

If and only if you are asking this question, option C is best. Except you cannot shadow in Zig, so what you would have to do is instead pub const foo = @import("foo.zig");

Uhh, not really sure what that means…

But that makes more sense. So a user can only use what is marked as pub in root.zig. Everything else is hidden.

what i meant is that the pub keyword works the same across files as it does across modules.

You can also directly set pub to the const of your import statement:

pub const foo = @import("foo.zig");

Zig compiler includes only the used functions. In an executable, used is anything called from main, while in a library, anything declared as pub in the root module is considered used.

1 Like