#Iterate std.meta.fields and switch over union variant

1 messages · Page 1 of 1 (latest)

soft trail
#

I'm trying to initialize some fields of a union. Since they don't have the same type, I have to match on their variants inside an inline loop. I can't quite figure out the syntax, though:

const Conf = union(enum) {
    modules: std.EnumArray(ModulesList, std.StringHashMap(void)),
    defaults: std.EnumArray(ModulesList, []const u8),
};

pub fn doSmth() !void {
    inline for (std.meta.fields(Conf)) |f| {
// this is wrong, 
//f.type gives me the type of the field, not its variant
        const mod = switch (f.type) { 
            .modules => std.EnumArray(ModulesList, std.StringHashMap(void)).init(),
            .defaults => std.EnumArray(ModulesList, []const u8).init(.{}),
        };
    }
}
pulsar hamlet
#

maybe you could do for (std.enums.values(std.meta.Tag(Conf))) |variant|

soft trail
#

And is it possible to retrieve the variant name? Is it just a matter of doing std.meta.Tag(variant)?

viral zealot
#

here's how I did it

const Conf = union(enum) {
      modules: u8,
      defaults: u8,
  };

  var b: Conf = undefined;

  inline for (std.meta.fields(Conf)) |f| {
      if (std.mem.eql(u8, f.name, "modules")) {
          b = @unionInit(Conf, f.name, 6);
      }
  }

  try std.testing.expect(b.modules == 6);
soft trail
#

mm… Is that the only solution? I don't love having to hard code the variant names

viral zealot
#

there is likely no way around it

soft trail
#

thx

pulsar hamlet
#

also look at @unionInit i’m assuming youll need it

soft trail
#

Ok.

#

The result:

const Conf = union(enum) {
    modules: std.EnumArray(ModulesList, std.StringHashMap(void)),
    defaults: std.EnumArray(ModulesList, []const u8),
};

const StrType = struct {
    modules: Conf.modules,
    defaults: Conf.defaults,
};
pub fn doSmth() !void {
    const my_str = .{ .modules = undefined, .defaults = undefined };
    inline for (std.meta.fields(Conf)) |f| {
        const name = @tagName(f);
        switch (f.type) {
            .modules => my_str.modules = @unionInit(Conf, name, std.EnumArray(ModulesList, std.StringHashMap(void)).init()),
            .defaults => my_str.defaults = @unionInit(Conf, name, std.EnumArray(ModulesList, []const u8).init(.{})),
        }
    }
}
pulsar hamlet
#

this compiles?

#

did you mean something like this?

const StrType = struct {
    modules: std.meta.TagPayload(Conf, .modules),
    defaults: std.meta.TagPayload(Conf, .defaults),
};

pub fn doSmth() !void {
    var my_str: StrType = undefined;

    for (std.enums.values(std.meta.Tag(Conf))) |conf_tag| {
        switch (conf_tag) {
            // probably replace these initUndefineds with what you actually want
            .modules => my_str.modules = std.EnumArray(ModulesList, std.StringHashMap(void)).initUndefined(),
            .defaults => my_str.defaults = std.EnumArray(ModulesList, []const u8).initUndefined(),
        }
    }
}

(EDITED)

#

@soft trail

#

sorry if i ended up confusing you with unionInit lol, usually when you're doing field stuff with unions it comes up

soft trail
#

I see

#

Oh yeah, that builds alright.

#

Thank you, I was running around like a headless chicken trying to figure this out.

#

Is the idea of using union variant's payloads as struct values too convoluted? Am I making my life un-necessarily complicated here?

#

I'd just like to see if I can use some enum keys as struct keys, to keep multiple arrays in sync.

#

The end goal is to have multiple EnumArrays, but to parse config files into them, I need an intermediat struct representation.

pulsar hamlet
#

why have Config as a union then?

soft trail
#

because the two variants aren't the same type.

#

If I have a union, I can rely on the enum (it's re-used in other parts of the project)

#

maybe it's an un-necessary step, actually

pulsar hamlet
#

could you describe your overall problem? like what are you trying to parse your config file into

soft trail
#

sure

#

I have two INI config files, the modules and the defaults file. Modules go something like

[INPUT]
JS: Volume/Pan Smoother
// other possible entries for each field
[GATE]
VST: ReaGate (Cockos)
[EQ]
VST: ReaEQ (Cockos)
[COMP]
VST: ReaComp (Cockos)
[SAT]
JS: Saturation

and the defaults go

[DEFAULTS]
INPUT = "JS: Volume/Pan v5"
EQ = "VST: ReaEQ (Cockos)"
COMP = "VST: ReaComp (Cockos)"
GATE = "VST: ReaGate (Cockos)"
SAT = "JS: Saturator"

So the idea is that I can have a base enum that describe INPUT/EQ/COMP/GATE/SAT and re-use that as base for each of the EnumArrays corresponding to the files.

#

Modules needs to get parsed into a Set, which is represented by a StringHashMap(void) and the Defaults get parsed into some strings

#

I might add another set of files at a later date. So, I'm wondering: what if I have the file names we're looking for in the config folder encoded as enum variants? That way the config struct can be created based on the variants of the enum, compiler lets me know if I have an un-handled variant somewhere, and when I run my loadConfig(), the code iterates through the variant names to find the correspondingly-named files.

pulsar hamlet
#

thats an interesting idea, so is defaults like "if modules doesn't have this then use this"?

soft trail
#

Yes

pulsar hamlet
#

gotcha

#

wait so is there gonna be a defaults file for every ini file?

soft trail
#

No, the defaults are destined to let the host app know what to do. This is in the context of an audio mixing console:
In the music software (aka the host app), my project needs to map the console's buttons and knobs to the controls of the current music track.

So user selects a track, my project checks the available modules it has in store, if they don't match any of the modules on the track, it loads the default

#

If they do match - map to the current modules.

#

Also, by having multiple EnumArrays with same enum keys, I can join the look-ups in the arrays in the style of database joins.
I think that might be critical for the speed of the look-ups: the user might have hundreds of tracks, and invalidate the track's configs left and right. So whenever there's a change, I need to re-validate, have the default fallback option, and re-map the current controls quickly.

pulsar hamlet
#

thats a lot to consider, i don't really see why you need a union though, i'd do something like this

const Conf = struct {
    modules: ModuleToMap,
    another_file: ModuleToMap,

    const ModuleToMap = std.EnumArray(ModulesList, std.StringHashMapUnmanaged(void));
};

pub fn doSmth(alloc: std.mem.Allocator) !Conf {
    const defaults = getValuesFromFile("defaults.ini");
    var conf: Conf = undefined;

    inline for (std.meta.fields(Conf)) |field| {
        const values = getValuesFromFile(field.name ++ ".ini");

        var conf_field = Conf.ModuleToMap.initFill(.{});

        for (std.enums.values(ModulesList)) |mod| {
            // parse file for value
            try conf_field.getPtr(mod).put(alloc, values.get(mod) orelse
                defaults.get(mod) orelse
                std.debug.panic("defaults file missing {s}", .{@tagName(mod)}), {});
        }

        @field(conf, field.name) = conf_field;
    }

    return conf;
}

fn getValuesFromFile(file_name: []const u8) std.EnumMap(ModulesList, []const u8) {
    _ = file_name;
    // TODO: read ini file into EnumMap
}
soft trail
#

Oh wow, I didn't know it was possible to use variables inside struct declarations

#

that's awesome

pulsar hamlet
#

a file is a struct, so it'd be pretty hard to do anything if you couldnt lol

#

its just namespaced global scope btw

soft trail
#

haha

#

What's the benefit of using the unmanaged hashmap? Is it because the values have to be allocated by the parser?

pulsar hamlet
#

unmanaged variants dont store an allocator so theyre smaller, and you only have to do .{} to init them instead of std.StringHashMap(MyLongAssTypeName, MyOtherLongTypeName).init(allocator)

#

but them being smaller is definitely more important lol

soft trail
#

Oh I see

pulsar hamlet
#

if theyre all using the same allocator anyways just put it in the conf struct

soft trail
#

Also, why do you need to do conf_field.getPtr.put() instead of conf_field.put()? Is that also because the allocator is not in there?

pulsar hamlet
#

.getPtr() is for EnumArray, .put() is for StringHashMap

#

you're doing getPtr to get a pointer to the stringhashmap associated with that enum, then putting a string into the stringhashmap

soft trail
#

I see. That's a pretty complete solution, then. Thank you very much!

pulsar hamlet
#

np zeroLike

soft trail
#

Soon I'll build a rocket ship. I'll start with the music studio, though.

pulsar hamlet
#

lol i think nasa uses c++

#

but maybe they should use zig hyperandrew

soft trail
#

They'll switch sooner or later. Rust advocates managed to get the white house to ask the world to use rust, after all.

pulsar hamlet
#

i'd say so too lol, feels like every day something written in c is exploited

soft trail
#

😅

cinder turtle
soft trail
#

yeah…