How to split a big Zig source file into smaller ones?

I have a big Zig source file.

It is already about 500 lines and will grow more.

I want to split it into several files.

In C# this is easy, but Zig does not support the same feature.

What is the Zig way to split big code into smaller parts?

I am thinking about structs with parent pointer working together.

But maybe there is another way?

And let’s suppose that much of the functionality is already in other structs, so the “big code” mostly works as a dispatcher.

It sounds like you’re asking about inheritance, which does not exist in zig.

Could you provide an example of your problem?

The Zig way is to not split. There’s nothing that corresponds to C#'s partial classes, which I assume is what you’re referring to. Unless you use a terrible editor, large files is a non-issue in Zig as long as the content logically belongs together.

2 Likes

Large files isn’t Zig or editor issue at all.
It’s just my problem - I don’t like large sources :joy:

t’s mostly a matter of taste — I don’t like having a lot of code in one source file.

It seems I can solve the problem by delegating parts of the dispatcher’s work to separate structs(separate files) ,
each with a pointer to the parent, and so having access to its “database” and functions.

But maybe Zig has a more convenient way?

1 Like

500 lines is almost nothing.
This C# OCD to do every class in one file is - IMHO - a very bad habit.

I will never forget the dedicated file at my job that someone made:

namespace Excel;

public interface ExcelItem
{
}
7 Likes

i wasnt saying you cant split up your code, you can totally do that.

You mentioned parent pointers which makes me think of inheritance. It sounds like you already have odd connections between things in that big file.

I encourage you to split it up, and not try to maintain those odd connections, but change them to be more stand alone.

ofc this is all assumptions without any knowledge of what you code actually is/what you are trying to do.

perhaps your editor can search and jump to symbols, that feature makes me completely forget the size of my files, as long as the code fits together.

not guilty

1 Like

Tbh, 500 lines is nothing. You should organize your source code by Zig modules, each in one file, and (IMHO) module files should have a good amount of meat in them, not artificially split into many small files.

But a few thousand lines per modules really isn’t a bad thing. It’s not the 1980s anymore where we had to split files into smaller ones because they didn’t fit into RAM :wink:

But if you want to split a big file into smaller files, then just move some features into separate .zig files and import them via const bla = @import("bla.zig"), but if you need to pass too much data into those ‘external’ functions it might be a sign that you have split into too small parts.

5 Likes

I didn’t split,
but lay on the floor with the printout and
connected the nested cycles
with a ruler :nauseated_face:

2 Likes

The question of how to split code into multiple files is completely orthogonal to the question of how to model inheritance or dispatching.

What exactly is your goal and what are you asking for?

In Zig you can both cram your entire program in one file or place every individual function in its own separate file. Because of how the language allows for circular imports/references between types there are very few restrictions. So if you have an aversion to 500+ line files* there’s nothing stopping you from organizing your code like this:

// MyStruct.zig

some_field: i32,
some_other_field: i32,

pub const doThing = @import("MyStruct.doThing.zig").doThing;
pub const doOtherThing = @import("MyStruct.doOtherThing.zig").doOtherThing;
// MyStruct.doThing.zig

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

pub fn doThing(self: *MyStruct, x: i32, y: i32) void {
    // ...
}
// MyStruct.doOtherThing.zig

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

pub fn doOtherThing(self: *MyStruct, message: []const u8) void {
    // ...
}

So you can organize code whichever way you want without being forced into using specific architectural patterns. I would recommend you to keep things as simple and straightforward as possible. Definitely avoid applying OOP-ish design patterns haphazardly if you can’t clearly explain and motivate to yourself why a particular pattern is the right choice for a given situation.

*Though I would personally advice you to ask yourself why you feel that way and try to get over it; the Zig compiler itself frequently features 10k+ LoC files so it’s by no means considered a “bad practice”.

11 Likes

@g41797 Have you considered using code folding with your editor?

4 Likes

great idea but no

old dog and new tricks :grinning_face:

1 Like

To me, folding is the old trick and fuzzy matching on symbols the new one :smiley:

2 Likes

I think both are good tricks.
Folding is good when the code is very complex and you want to look at only a few different pieces of it and edit them in parallel, making it easier to look at the parts you are currently interested in and making more efficient use of whatever screenspace you have available (so especially helpful when working on small screens).

I think fuzz matching and jumping around is helpful when you can keep enough context in your head or have big enough screens so that you can put different parts of the code on different splits/views of the code etc. and when the need to jump around doesn’t exceed some level, where I would rather put all the relevant pieces of code on the screen at the same time, so I don’t have to jump around and build up context repeatedly.

Also another good use of folding is to temporarily get a sort of outline of the file, to get an idea about what are the overall contents of the file.

I also had a symbol outline in lazyvim which was also good for that, but that stopped working properly and I haven’t found out yet why it now only shows functions and not types, namespaces, etc.

If you haven’t thought about where this preference came from, it’s worth thinking about. I used to be averse to large files, because as a JavaScript developer, I was trained to break everything up as much as possible. It wasn’t until I learned Elm that I got over it.

Not to yuck your yum, if you want to keep your files small, go for it. :slight_smile:

For anyone else that finds large files cumbersome, there’s lots of editor tricks you can use to make it easier.

  1. Bookmarks can be useful, especially if your code isn’t broken down on an LSP level. (i.e. M, V, C, or css, html, scripts)
  2. Jump to symbol will let you see all the available variables, methods etc in the file, and immediately jump to any of them.
  3. Careful use of vim’s % key or treesitter enabled editors will let you jump to the next function, allowing you to quickly traverse files with lots of definitions in them.

Again, if you like small files, don’t let me stop you. Some languages have more influence on file sizes than others, but in general it comes down to how you like to abstract and how you separate (or don’t separate) your concerns.

4 Likes

Same here! It was very fortunate that I got introduced to this concept early on in my life. In JS-land, I found it very annoying to have to jump around files just to string things together mentally, because someone decided that one file = one function. Glad to see a talk that hits home how things should be structured.

Here is the talk from Elm creator for those interested: The life of a file

1 Like

Namespaces.

When you have a set of common functionality that operates on the same struct, enum, union, or opaque type, move the logic into the respective namespace.

Then, use the suggested namespace conventions:

This will cause your source to become a tree of types.

Finally, choose a sub-tree, and move it to a separate file. Files are structs.

Repeat this process until you are satisfied with the size of your files.

Make sure you have some kind of zig fmt --check on save integrated into your editor; this will make the cut-and-paste operation much swifter.

9 Likes