Generic types and source file structs

Relevant doc here and here and, from an earlier thread, the maybe-zen: “avoid repetitive namespacing” (like foo.bar.bar, but maybe even foo.bar.Bar - debatable).

So, for my generic structs, I’ve been using:

fn Baz(comptime T: type) type {
    return struct {

Great. No problem. But I confess that I like the source file struct motif, especially because I think I’d like to break my structs into files of their own anyway, for this project.

If they weren’t generics, this would be a no-brainer, and I’d be able to avoid repetition in namespace (like foo.bar.Bar), as well, because Bar.zig would be a file, rather than bar.zig having a struct named Bar.

But… how… with generics? I know it’s pretty idiomatic to cap-name the function if it’s a function that returns type (as above, Baz), but I’m wondering about a different approach, such as:

foo/
  bar.zig

where bar.zig contains a lowercase function, factory-ish, but to generate the specific type (not shunt to creating an object simultaneously). So:

//bar.zig
fn makeType(comptime T: type) type {
    return struct {
//...

//main.zig
const Bar = bar.makeType(u8);
     // for some simple app that only needs one kind of Bar

// or, more likely, for an app that needs variants:
const TransmitterBar = bar.makeType(Zag);
const ReceiverBar = bar.makeType(Zug);

Note, this Bar is not intended to be super general-purpose, like std data structures - these generic structs are pretty specific to the domain, so there’s slightly less incentive to be bound to do it “the std/general way”, in every regard.

I should also say: when I have these cases where I want to “make” a specific type, like Bar(u8), and const-NAME it, I don’t love naming it BarT or BarType or or even BarU8 (though this is what I often do currently)… this only crops up in the top usage, above, where the program (or whatever) really just thinks of this as “this program’s Bar”, so a specific name like TransmitterBar doesn’t lend itself naturally. IF I use a lowerCaseFunction() maker, I can just name the ultimate type Bar, as there’s no conflict. (I know, another way to do that would be const Bar = foo.bar.Bar(u8) or something of the sort, and just make a local version of the name, but even though it’s scoped, I don’t love this.)

So, 1) is the above proposal heinous for some reason I’m overlooking?, 2) is there another way I should consider - a way that might not be as common, but is clever to my hopes and possibly more idiomatic?

Yes. The compiler assigns a name to types, this scheme would leave makeType(u8) as the primary name of your returned type. The identifier it’s assigned to plays no role, unlike when a type is constructed directly.

So unless you can stand looking at foo.bar.makeType(u8) and foo.baz.makeType(usize), I suggest not doing this.

I think I COULD stand looking at BarA = bar.makeType(u8) and BarB = bar.makeType(Bomb)- that actually feels very comfortable to me, and I guess, from what you’re saying, that would wind up embedding (in that name the compiler assigns) “bar” for analysis. Right? (I would only choose to write out foo.bar.makeType() if there was another, like baz.bar, that followed the same pattern, such that “bar” would not be sufficient to tell them apart. Perhaps, for example: const v1Bar = vendor1.bar.message.makeType(u8) and const v2Bar = vendor2.bar.message.makeType(u8)- in this case I might not care about the “bar”ness of the two, in my CODE, though, as you say, the compiler would assign values that include the “bar”, and that would be fine. I should also indicate that my supposed use is kind-of “once at the top”, for app setup, and I’m always assigning the new type to a name of its own that is sufficiently useful for my context (even forgetting it was a generic construction thereafter). From there on out, twenty variables may get the type v1Bar, but we’d never see the vendor1.bar.messagechain again, after the initial type-fabrication. All this detail change anything?

But the point is excellent - I’m thankful for the reminder.