Calling non-pub function outside its container or namespace

I thought that non-pub functions are only visible inside their container (namespace). Now I am confused because the following code runs fine but IMHO should generate a compile error:

//! foo.zig
const std = @import("std");
const stdout =;

const Foo = struct {
    fn foo() !void {
        try stdout.print(" called\n", .{});

pub fn main() !void {
$ zig run foo.zig called

What gives?
I am running 0.12.0-dev.1834+f36ac227b.

1 Like

pub specifier exposes a given function or container to be imported from another compilation unit.

Does it mean that inside the same CU pub or no-pub does not matter unless one uses usingnamespace to force drop non-pub declarations?

Yes, visibility is enforced per CU. I guess you can employ usingnamespace in a contrived way to redefine what’s available to different namespaces within the same CU, but that’s not advised, by design.

“Compilation unit” is confusing here, especially with #18474. “Source file” would be more appropriate, right?


Hope this won’t get merged for several reasons, the main one being that slapping newly invented abbreviations onto every codebase component is a bad practice that severely restricts readability and makes the learning curve for newcomers only steeper.

As correctly pointed out by @cancername pub visibility is enforced by @import of the source file regardless of compilation unit. Only pub declarations are carried over by the @import. Unfortunately, usingnamespace does not seem to help. If all the code fits into a single file presence or absence of pub visibility qualifier does not seem to matter.

It can be explained in a doc comment. One of Zig’s strengths is the ability to make breaking changes to anything and everything. For more rationale, or if you’d like to contribute to the discussion, see: build system terminology update: package, project, module, dependency · Issue #14307 · ziglang/zig · GitHub . Quoting mlugg:

The use of the term “compilation unit” is intentional because it’s referring to exactly the same concept: a Zcu represents a collection of Zig sources being compiled into a single object file. The difference compared to C is that in the latter, each source file generates a separate object, and hence each C source file is a separate compilation unit.

1 Like

Yeah, my bad, I was thinking of the CU in the context of C. I do agree with the use of that term for Zig, it’s just that the abbreviated Zcu is obscuring that terminology and will take some time getting used to.

1 Like

That makes sense :slight_smile:

1 Like

The fact that pub only affects visibility of @import-ed declarations is unfortunate. It mixes up logical and physical organization of the code. Mere moving a struct declaration between source files can result in compilation errors.

It did seem that way to me at first, too. I don’t believe this approach is that common, which is probably why it’s hard to see its merits right away. But I came to appreciate the fact that in Zig physical organization IS the logical organization of code. That way it inherently forces the source tree to reflect the code logic, rather than permitting arbitrary code structure.