Why does @field work for declarations?

Oh, that’s a different story.

So @tagName does not automatically embed expensive string conversion in your program. That only happens if the strings survive until runtime. At comptime, it’s no problem.

1 Like

Right. I didn’t suppose there’d be any “expense” at runtime, except maybe if I used StaticStringMap; I think what @hachanuy was saying is that the compilation itself would bloat the binary with the inline-for @tagName()s all over. So, bigger binary, maybe “who cares”, or maybe it’s enough to dislike. I haven’t tested. But I’d guess that all of the options on the table aren’t expensive in terms of runtime speed or memory usage (again, StaticStringMap would at least require more stack for the umpteen strings… unless that’s not exactly true since even the init()s of EnumMap and EnumSet create temporary strings for every enum via @tagName).

I’ve still to play with @tholmes’ e2_valid_for_e1(), but it has @tagName (twice) in it, too, and he already identified a shortcoming of that approach, too… I can’t visualize the real gain that his idea has over the EnumMap/EnumSet idea, so would have to just try, and profile, if I felt it was likely to offer real gain. I also haven’t yet tried @Sze’s comptime-function-to-generate-subset-enums with identical backing ints (that’s the key difference); I can see the advantage this offers to “converting” from one enum to another, though I actually don’t foresee my needing/doing that - my vision involves a pretty straight path, where, e.g., database values (people-names, email addresses, the usual stuff) will result in a “build up” of the data structure for the purpose, and it’ll get serialized, sent, and realized. No real “manipulation” in the middle. So one advantage of @Sze’s clever scheme may not really benefit me this time. But I’ve still got to unpack it better, and try it, to see if I see other advantages over the simple EnumMap / EnumSet version.

Nuance may still invite comment, but what I’d be hopeful for is somebody saying, “No, here’s a major problem with your EnumMap / EnumSet idea: …”, if, indeed, I’ve missed something. And, though I think that implementation is superior to my OP poor-man scheme without any std structs, it’d be nice to get a thumbs-up from somebody in the know, too, that the EnumMap / EnumSet is indeed a right-way improvement. Thanks all.

@tholmes, I’d like to understand this more - where do you get this intel? By “turn the EnumMap access into a true comptime-known boolean”, I thought, at first, you meant, by “access”, the call to contains() (meaning, make it a comptime contains()), but I don’t immediately see how that works. I’ll trust, if that’s what you mean, but I’d like to know if I’m interpreting you incorrectly.

2, since that was your “edit”, perhaps it’s intended to negate some of what you said above… but I’d like to know what you mean by “still perform a memory access” - it seems impossible that it’s doing any allocations, so I’m guessing you just mean a stack look that could be avoided if it weren’t for the optional unwrap. I feel like that’s probably pretty marginal cost? But perhaps you’ve got reason to believe it could be more significant? Did you profile it against your table struct type?

Thanks for the help; I’d like to benefit from your discoveries rather than rehashing it all, if possible.

Hi,
I mainly got this info by using https://godbolt.org/, specifically this session.
You can see how I “comptime-ified” the map access, and how if you delete the comptime keyword, it reads from an “example.map” location, and emits a test instruction, whereas if you don’t, it doesn’t and just generates no code for the main function.

You can also see how the Table struct is declared and how e2_valid_for_e1() is also “true comptime”, but I highly recommend you don’t take too much inspiration from it, since your map declaration is much shorter and this session literally proves how its performance is completely equal.

Thank you for the lead. I’m trying to get my head wrapped around it. You’re referencing my earlier post, it seems, prior to my EnumMap-EnumSet proposal, but you conclude with

which sounds attractive, if I’m hearing correctly. In other words(?), instead of a .contains() call in, e.g., an EnumSet (to check validity), an attempt to simply make an assignment (that is, in the HTML example there, an attempt to set the action attribute to, say a div, rather than a form), would be set up to simply Error-return right then and there. No .contains() call/check performed at all. Do I have this right? If so, I just have to tie the pieces together. I think you gave me enough hints to do that, but I’ll have to scratch my head a little more to land it.

I think you’re saying: don’t actually assign the subset enums to the Tags-enum value (in which case the enum values would be the same by definition); rather in this comptime function, generate a brand-new enum, but make the names the same, and the enum values the same , explicitly. So, instead of that

const ValidAttributes = struct {
   const accept: []const Tags = &.{ .form, .input };

I’d have

const ValidAttributes = struct {
   const accept: generate({ .form, .input });
//...
comptime fn generate(tags: []const Tags) type {
   const field_names: []const []const u8 = std.meta.fieldNames(tags);
   // ...
   return @Enum(<backing_int>, .exhaustive, field_names, field_values); // ??
}

… ish? Perhaps I’m way off?

Ok, wow, this is very enlightening, thank you. I had not yet adventured into godbolt. I see the huge difference between comptime map.get(… and map.get(… (without comptime) (and the lack of any difference by removing the comptime in e2_valid_for_e1 @haField) … the important takeaway is that I should comptime those map.get() calls, if I can, if I go that route. I haven’t yet worked out all where all the get() checking would be - some might only be possible at runtime… so this is valuable insight indeed. Thank you.

This still baffles me, though (the runtime, or non-comptime-explicit variant) – is this what you’d expect? Or is this a bug? And do you still think it’s rooted in the optional-unwrap? Would you guess that if-accessing the optional, rather than ?-unwrapping it, that it would be any different? Well, I tried a little modification in godbolt (pardon the stupid naming):

    if (comptime map.get(.a)) |aa| {
        if (!comptime aa.contains(.y)) return error.Unexpected;
    }

and, indeed, it blows up if I remove the second comptime (inner if). I can’t remove the first comptime independently, of course, as that would make the inner scope not comptime.

And what a crazy blowup indeed! It feels “wrong”… but I don’t understand that giant splat of assembly.

And what a crazy blowup indeed! It feels “wrong”… but I don’t understand that giant splat of assembly.

Yeah, my bad. Most of that assembly is actually related to error handling.
You can see things more clearly if you change main() to no longer be able to return an error, and change the if statements to instead execute unreachable if they fail.