New Idea for file level structs

I’m not sure if this has been discussed somewhere already, but I wanted to get feedback on this idea, whether it’s feasible and whether it aligns with the direction of the Zig language.

As we know Zig has file level structs that we can define as follows:

const Foo = @This();

value: i32,

pub fn init(val: i32) Foo {
    return .{
        .value = val,
    };
}

and we can use it like this:

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

const foo = Foo.init(10);

I wanted to ask if this could be possible for union(enum)

const UnionEnum = union(enum) {
    A: struct {a: i32, b:i32},
    B: struct {c: bool},
}

can be:


// We can define something like this:

const UnionEnum = @union(enum);

A: struct {a: i32, b:i32},
B: struct {c: bool},

and it would work as same principle.

This is just an idea that I came up please don’t take it too seriously just wanted to learn if its feasible or not.

Thank you in advance.

4 Likes

I see one problem with this: Every file is always a struct. You dont need to do anything for that and you can’t do anything against that. If your example file is completely empty, you can still import and use it as a struct. It would be empty as well, of course. My point is that there is no place that says “this file is a struct" that you could change to say “this file is a union" or whatever.

3 Likes

Yeah I guess so, if someone really wanted to implement this I guess the representation of file structs needs to change and I don’t think that’s feasible. I just get annoyed sometimes because of this: const UnionEnum = @import("union_enum.zig").UnionEnum; because I need to include with another access. It makes me feel like I am doing something wrong but I know there is nothing wrong.

It would be nice. As long as it’s just about making that a bit nicer though, I doubt it will ever happen. If there would be some real advantages to it, it would be more likely.

By the way, I found a thread about basically the same topic. It also hasn’t a lot of discussion though: .zig files as enum containers

It would also be awesome if a file could be a function or a const value (the latter would make zig a configuration file format). You could do something in the file extension, e.g. pi.const.zig could just contain some approximation of pi, generated by a block.

How is that more ergonomic than something like @import(constants.zig).pi?

1 Like

Because it would give a standard config file format. There are at least ziggy and Zon trying to do this, but they are restricted by not actually being zig.

I thought about this a bit myself.

In Zitron, the parser-compiler can emit the token enums as their own file. In Zig there’s no special reason to prefer this, because circular imports are allowed, so a separate tokenizer file can import the enums from the parser, and the parser can import the tokenizer, and everything just works. But it’s also nice not to do that.

zitron names that file after the %token_enum_type, defaulting to TokenKind, so, TokenKind.zig. But this is irregular, because it’s a namespace file, not an instantiable-struct file. I didn’t think this small deviation from doctrine was worth correcting.

Then importing that looks like this:

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

Which is, not bad, not great, a few sacrifices are acceptable when generating code. In my opinion at least.

Still, “wouldn’t it be nice” if the file could just be the enum? Syntactically, no problem:

//! This file is an enum
enum(u8); // Illegal expression currently

fee,
fie,
foe, 
fum,
// Whatever else

But this goes from “files are just structs” to “files are structs by default, but they can actually be any container type, be sure to check for a special little bit of irregular syntax at the top”.

Which I’m not completely opposed to, part of me continues to think that would be neat.

But it brings no new abilities to the language, it doesn’t lead to safer or more efficient code, it’s just sugar. I would say it makes import statements cleaner, but makes program structure slightly harder to understand, and expands the answer to “what is a file” from a short sentence to a fairly long one.

I’d call that a wash, but even if we say it’s net beneficial, that benefit is fairly small. All things considered it doesn’t seem worth it.

I don’t even use CapitalizedStruct.zig files because they’re not fully capable structs without generics. (I realize an fn is needed for generic structs.) I think of files as purely namespaces, even though they are structs of course. This is simpler and more consistent to me.

1 Like

I tend not to myself, for a similar reason: it’s not uncommon that I decide to turn a struct into a type-returning function, and if the struct is a file, that stylistically invalidates the name of the file.

I don’t treat this as a rule but it’s definitely my habit.

2 Likes

For me too. Apart from the fact it looks ugly in my file-list.
I rather have a type position.Position from a file position.zig.

2 Likes

I’ve wanted a file level union(enum) myself for nearly as long as I’ve been writing zig. About a year later, I care a lot less. Would it be nice? Probably, would it be worth the cost to zig’s complexity budget? Absolutely not. const Thing = @import("thing.zig").Thing is not a perfect and as clean as I want, but truth be told, as soon as I type git commit I don’t care anymore. I’m ok with it. File level structs are a very nice bit of language sugar, but the namespace for the whole language doesn’t need to be perfectly flat. Especially when you also have to answer the question: are you sure that union doesn’t belong somewhere else?

2 Likes