Zsort: import organizer for zig

zsort: import organizer for zig


I was missing isort from Python, so I built zsort for Zig: GitHub - mstdokumaci/zsort: Opinionated import organizer for Zig similar to isort / goimports · GitHub

I am using zlint and zwanzig on my Zig projects, but neither of them sorts the imports, and I like my code tidy. std.zig.Ast made my life easier building this project, can’t tell the same about trying to support both 0.15.2 and 0.16 at the same time.

AI / LLM usage disclosure

I used cheap LLMs for doing the hard work from the beginning to the end. But I have been coding since 1996, so I hope this is still not vibe coding.

6 Likes

I use your tool in my project, and it just work! The code is more cleaner. Thank you!

1 Like

Thank you for this lovely feedback! Great to hear the dev satisfaction with tidy code.

Looks nice :slight_smile: Any thought about supporting bottom-of-file imports? I’ve been trying that out recently since the language lets us do this

1 Like

I am unaware of the concept. Let me research a little bit and understand better.

1 Like

It was discussed here Rationale behind @import at the end of file

2 Likes

I see, this is an interesting one. I have built zsort with the assumption that each import gets assigned to a const, then potentially some aliases are created.

For example, one pattern is first const a = @import(“a.zig”) followed by const x = a.x and const y = a.y, and another pattern is direct const x = @import(“a.zig”).x; of course, it is always possible to just const a = @import(“a.zig”) followed by no aliases.

zsort tries to group all of these neatly at the top of the file. If we move the entire block to the bottom, don’t we risk constants being used before they were defined?

Language Reference: Container Level Variables

Container level variables have static lifetime and are order-independent and lazily analyzed. The initialization value of container level variables is implicitly comptime. If a container level variable is const then its value is comptime-known, otherwise it is runtime-known.

The file is a struct with container level variables which are order independent.

1 Like

Wow, I just learned something new. A const being an implicit comptime makes sense, and I do understand how you can move container-level consts around safely by that logic. Still, it requires some unlearning for people used to strict order of operations in many other languages. So I think this will probably remain a matter of taste for another generation of Zig developers. I think the cheapest and the least controversial change to zsort is a flag to push all imports to the bottom of the file, for developers of that taste. I will create an issue and pick it up on my first availability.

just released v0.7.0 supporting a --bottom flag to group all imports at the bottom of the file.

  1. std / builtin — the std and builtin modules
  2. Third-party — other module names (httpz, sqlite, …), including @cImport
  3. Local — paths containing / or ending in .zig, plus Zig’s package-level modules root (the package’s own root source file) and build_root (the build runner’s root module)
  4. Aliasesconst X = module.Member; where module resolves to an import above; const X = @This(); sorts first in this band

The rules i’ve been using are this:

  1. Module imports: const std = @import("std") and const foo = @import("./foo.zig") are grouped together. std is not given any special treatment. Sorted, alphabetically if i get around to it.
  2. Module Aliases: const mem = std.mem for example.
  3. Struct/Function Aliases/imports: const Allocator = mem.Allocator, const Io = std.Io

That’s just my personal preference, not that i expect you to implement it. I don’t think the specifics of the rules are super important, as long as there are rules that make it so I don’t have to think about it when I add an import. The ones above work pretty nicely, so far i haven’t found any ambiguous case where I’m not sure where to put an import.

thanks for the update, used it with the new flag and it works great!

1 Like