Zig's "Module" System

Am I the only one not totally in love with Zig’s module system? A lot of the language is really great, but there are two things that just kinda piss me off: the module system, and files as structs. Let me explain…

So, I was just recently trying out libvaxis to make a TUI gemini protocol browser, and it was mostly going fine. I got borders in child windows and a text input that works quite well for the address bar. And then I started looking at the TextView so that I could display the content text in a scrollable view.

So, I’m looking at the TextView file, oh… sorry, I mean struct… or… struct-file (makes gagging motion) and I see that I think I need a BufferWriter in the… struct, right, that’s what it is. Definitely not a namespace or module/package. This BufferWriter seems to require me to pass in a pointer to a DisplayWidth.DisplayWidthData somehow. I see that DisplayWidth is “imported” like so: const DisplayWidth = @import("DisplayWidth");

So I Ctrl+Click in my fancy text editor on this import string, and somehow magically I end up within the zg library code! Like, WHAT?!?!?

There are so many things wrong with this, but just the fact that the import doesn’t tell me the real module/project/library/what-the-hell-ever is, well that just seriously pisses me off.

Things like this make me want to go back to Odin or Golang…

Just to clarify, my problem with zig’s module system is that:

  1. It conflates “modules” and structs.
  2. It is not clear to me if files are modules or the things you put in the build system are modules. You import both the exact same way, and modules can expose other modules, and modules and struct-files also expose other struct-files.
  3. It’s a flat system, there’s no “zg.DisplayWidth”, it’s just magically “DisplayWidth”, unless…
  4. You expose struct-files and modules that are imported as constants… which again leads to my final point:
  5. It conflates “modules” and structs/values/constants/variables.
  6. And one additional problem for good measure: modules can be defined in the build system without being defined in the file/directory structure.

This system might be “flexible”, but it’s also chaotic, disorganized, unintuitive, overly dynamic that it creates inconsistency and absence of structure, and reproduces the exact same terrible design decisions of C and C++.

I’ll try to make this as clear as possible

A namespace is a named list of declarations, in zig a type meets this definition, so zig chooses to just use types as namespaces.

If a file is a namespace it must be a type, what kind of type, a struct, it is probably the most common kind of type, but it’s still a pretty arbitrary decision.

The language has no concept of modules.
The build system has a concept of modules, it’s just a root file and a list of imports (ignoring the c/cpp stuff).

You can import a file or something provided by the build system, dependencies are necessarily provided by the build system.

Because namespaces are types, and types are values, when importing you assign to a constant.

the system is not inherrently any of that, it is entirely up to how its used. the system works this way because it meets a lot of zigs needs, without it, there would need to be a lot of special case things to accomplish what zig uses this system for.

4 Likes

some examples of how zig uses this:
the build runner is built with your build.zig as an import,
the test runner is built with your code as an import

the standard library imports the root of you module and looks for std_options which let you configure some aspects of the std library, such as a panic handler, custom logging etc, etc

1 Like

@vulpesx

Yes, in fact, it is, and it is because as you’ve said, it is entirely up to how its used, just like, oh I don’t know, dynamic typing, for example? Flexibility is not always a good thing, and being overly flexible creates chaos and disorganization. Flexibility also means there’s no one standard way of doing things, which makes things unintuitive. Lack of standard is chaotic and unintuitive, and you can just look at the web in the early 2000s to see an example of how that’s true.

I think this system is horrendous, and I think that the fact that every other language is moving away from this system is because the system is horrendous.

The very fact that you say the language has no modules is exactly my point - it conflates files and modules! It even conflates structs and files!

Ok, no. Zig chooses to make types a “list of declarations” - declarations not being the same thing as fields. And like… fine. OOP basically does a similar thing with classes.

And yet this does not necessitate that files should be structs. In fact, files really should be packages or modules. Yet again you are proving my point that namespaces and modules and structs and files are all conflated.

I do not think this is a good system at all. Again, being overly flexible creates chaos and makes things unintuitive. Zig is basically generalizing code structure. If Zig wants to be Lisp, then it should just be lisp.

To be clear, this is a choice that zg made.

3 Likes

A choice which was not prevented by the compiler or build system…

A module provided by the build system will not end in .zig. A file will be a path, and thus end in .zig or .zon.

When importing from a file, you cannot import above your root source file.

For an executable, your root source file is where your main function is.

The root source file defines a module.

So in summary:

Files are struct types.
Namespaces are struct types.
Namespaces are not a language feature. By convention, struct types that do not have fields are considered namespaces.
A module is a build system concept.
A module is defined by its root source file.
Modules and files can be imported.
The result of an import is a struct type.
Modules are struct types.

2 Likes

I would argue that it doesn’t conflate, it simplifies.

Yes, flexibility isn’t always good
yes, flexibility allows for chaos and disorganisation.
No flexibility does not remove standardisation, the allocator interface is a great example of that.

zig has decided that this flexibility is worth it, at least for the near future, it could change it could not change, we will see.

2 Likes

@kj4tmp @vulpesx If both of you think condescendingly explaining things I already know about the language is going to make me change my mind, then you should spend your time on more important things, because it’s not. It only further drives me toward the opinions I have expressed above because your explanations support every single point I made - zig’s importing system sucks and is quite possibly one of the most idiotic things I’ve seen in a while.

I’m ultimately giving feedback about zig and how the fact that structs and the lack of hierarchy in module names (and the flat structure and lack of modules/packages in general) makes code unintuitive, confusing, less readable, and less self-documenting.

Whether the community wants to take it or not does not matter one bit to me, except that I now know to stay away from Zig, because I do not feel comfortable in the future of the language any more when the community creates insane justifications for bad early design decisions.

Zig’s allocator interface doesn’t remove typing. Calling all files and modules and namespaces and types as structs does remove typing.

sorry if i came off as condecending, i was just trying to explain zig.

can we please not make this hostile

3 Likes

I don’t mean to be condescending, I mostly approach my responses with the goal of building our corpus of information about zig so that people can search for information. Just like how stack overflow has all the answers…it takes time to build that.

4 Likes

You posted in the explain category and included some questions and misconceptions about how zig works.

You have expressed your opinion on zigs import system, if you want to talk about potential changes post in the brainstorming category with language and build-system tags as this relates to both

Please refrain from accusing people of being condescending, we definitely do not intend to be.
Please refrain from insulting people or the language.
These things prevent productive conversation about zig, the entire purpose of ziggit

7 Likes

Something I found odd about the module system is the large number of places I can rename a module.

I understand the purpose but I can’t help but wish for a simpler system that this:

Dependency name (build.zig.zon) (renameable)
Module name (inside my dependency)
Module name (that I expose to my source files)

Probably unavoidable to allow name collisions.

And the only way for me to obtain the module name inside my dependency is to read the build.zig of my dependency. This problem also exists with python, but the reverse. Sometimes I’m looking at a GitHub repo that I want to use, but I can’t figure out what the packages name on pip is…

4 Likes

You example is telling you exactly what the real module is:

The real module is “DisplayWidth”. It was added with this name in the build.zig.

It sounds like you just don’t understand the code that you’re reading, which is normal whenever we approach a new code base. No language can save you from this. I don’t see what this has to do with imports.

I don’t know what Ctrl+click is supposed to do. I can follow imports just fine.
If you were trying to follow a type defined in the zg library, where did you think the LSP was going to take you?

4 Likes

None of the examples you’ve shown here depends on or necessitates Zig’s design. In fact, I don’t think zig’s design has anything it can do that other systems cannot do. Files as structs have absolutely no use, and the lack of proper modules/packages in the language doesn’t make it so you can have options on the standard library.

I think removing a whole set of organization/structure to the code is a whimsy justification for something as tiny as specifying options for the std library. It’s just silly.

we are not trying to change your mind, we are not trying to justify zigs design choices, we are not trying to debunk your points.
we are simply trying to explain zig, since you posted in the explain category and expressed a lack of knowledge about how zig dealt with modules, in particular you were not sure if they were a language construct or a build system construct.

I don’t know why you seem to be taking this as an argument

1 Like

I’ll just finish with the following story:

One time in the recent past, in the 1970s, a group of people set out to create an operating system much simpler than previous OSs and one that could run on teletypes. Because it ran on teletypes, most of the commands had to be designed within those constraints, and so commands printed out cryptic and short error messages when they failed, and printed out nothing when they succeeded. This OS ended up becoming one of the biggest OSs of the time, and tons of people were using it.

Fast forward some years, and new operating systems were created that had better error messages. But a group of people latched onto Unix and would not let go. Those of us using Dos criticized Unix for showing cryptic error messages, but the Unix fan-base told us how stupid we were, because clearly Unix was designed to not have readable error messages for benefits a, b, c, d, e, f, g. These Unix fans go on to make another operating system inspired by Unix where error messages were just as cryptic, because clearly that’s a great design decision!

Meanwhile the Unix developers move on to create a new system called Plan9. Because they were not constrained by teletypes, they were finally able to create a much better design with better more explanatory error messages!

As it turns out, Unix’s cryptic error messages were the result of hardware constraints, not because they are better design.

This is a story as old as time. People create decisions within the constraints they have. As the times change, the constraints change, and new decisions are made. But a group of people always take the original decisions way too seriously and hold to them regardless of the fact that decisions are only as good as the constraints they are in, only as good as the times they are from.

C was originally a one-pass language with a preprocessor that had to fit into a certain set of constraints. Zig’s constraints are not the same as C’s was at the time of its creation. Our compilers can be more complicated because we have better hardware. C’s design was not the best ever, it was what was needed at the time. Zig should be improving on C’s bad import system, not replicating it.