#Can't use ComptimeStringMap during inline for

1 messages · Page 1 of 1 (latest)

gusty dawn
#

So I have this test which fails:

  const example = // this is some ini file format
      \\ [core]
      \\     repositoryformatversion = 0
      \\     filemode = true
      \\     bare = false
      \\     logallrefupdates = true

  var fbs = std.io.fixedBufferStream(example);
  var parser = parse(std.testing.allocator, fbs.reader());
  defer parser.deinit();
  const result = try readToStruct(MyStructType, &parser);
  try expect(my_struct.some_field.filemode == true); // this fails

In order to parse the example, the code goes through an inline for loop. The loop iterates the fields of the type, and calls a convert function on the key-value pairs:

const truthyAndFalsy = std.ComptimeStringMap(bool, .{ .{ "true", true }, .{ "false", false }});
pub fn convert(comptime T: type, val: []const u8) !T {
    return switch (@typeInfo(T)) {
        .Bool => truthyAndFalsy.get(val).?,
        else => @as(T, val),
    };
}
// mid inline loop:
@field(innerStruct, field_name) = try convert(my_type, value);

Aaand I can't figure out why the conversion doesn't actually happen.

  1. Is there something glaringly wrong about this code?
  2. How can I print to console during tests? I tried std.log.warn("\n{any}\n".{myval}) but nothing comes out. The debugger's also not really helping
tiny basin
#

If you mean the @as, then the error might be misleading

#

@as is for lossless casts only

#

Basically just type widening

#

It only does explicit coercion

#

Same as const something: T = v;

#

const something = @as(T, v);

#

Not clear to me what you're actually trying to do in that example; val is a string and you cannot use a cast to convert a string to much of anything else.

#

What are you trying to achieve with that switch prong?

#

Might want stuff like std.fmt.parseInt.

gusty dawn
#

Is the @as really the issue here? That's weird

#

I get that the @as block doesn't do anything, I guess I should just return an error in this case.

#

Switching it to an option:

#
pub fn convert(comptime T: type, val: []const u8) !?T {
    return switch (@typeInfo(T)) {
        .Int, .ComptimeInt => try std.fmt.parseInt(T, val, 0),
        .Float, .ComptimeFloat => try std.fmt.parseFloat(T, val),
        .Bool => truthyAndFalsy.get(val).?,
        else => null,
    };
}
tiny basin
#

You don't pass that into parse anywhere

#

Or return it AFAICT

gusty dawn
tiny basin
#

No, I mean in this line:

  try expect(my_struct.some_field.filemode == true); // this fails

#

Are you testing the right thing here?

gusty dawn
#

#L187 actually

tiny basin
#

On L106 do you mean parseInt(T, val, 10) ?

#

Last param is the base IIRC

gusty dawn
#

oh yeah

#

that's a problem.

tiny basin
gusty dawn
#

That's still not what the issue is, though: my problem is that the booleans are not getting converted.

tiny basin
#

Good in these situations to sanity check

gusty dawn
#

I can't figure out if it's because the code doesn't go into the conversion or what

tiny basin
#
test {
   const result = convert(bool, "true");
   const result2 = convert(bool, "false");

   try expect(result == true);
   try expect(result2 == false);
}
gusty dawn
#

Wait, convert is supposed to be returning a Type

tiny basin
#

L141 says otherwise

gusty dawn
#

oh you're right

#

sorry

#

Let me push the update

#

Alright, I converted the result to an option

tiny basin
#

Wait

#

I see the issue

gusty dawn
tiny basin
#

L141 mutates innerStruct, but you don't update ret_struct.

gusty dawn
#

0_0

#

Is that problematic?

tiny basin
#

var innerStruct = @field(ret_struct, ns.name) copies that field into innerStruct

#

So you mutate that copy

gusty dawn
#

you're kidding

#

those assignments are all copies?

tiny basin
#

All assignments are always byte-for-byte shallow copies

#

Anything else is insanity

gusty dawn
#

haha ok

tiny basin
#

That's why you'll find pointers everywhere 😄

#

Consider const innerStruct = & @field(ret_struct, ns_info.name);

#

[@field(x, y) is identical to x.y semantically]

#

[So &@field(x, y) is &x.y]

gusty dawn
#

So if I try writing it in the code

#
                        var innerStruct = &@field(ret_struct, ns_info.name); // err local var is never mutated
                        inline for (std.meta.fields(@TypeOf(innerStruct.*))) |key_info| {
                            const field_name = key_info.name;
                            // if we find the current key

                            if (std.mem.eql(u8, field_name, key)) {
                                // now we have a key match, give it the value
                                const my_type = @TypeOf(@field(innerStruct.*, field_name));
                                const conversion = try convert(my_type, value);
                                if (conversion) |converted| {
                                    @field(innerStruct.*, field_name) = converted;
                                }
                            }
                        }
#

Can I just do the updates without de-referencing?

tiny basin
#

Zig got rid of the [C-ism] where you have two different operators for val vs ptr (x.y vs x->y), and so you can just use x.y in both cases.

#

Meaning that x.*.y is the same as x.y

#

So you shouldn't need to do the deref yourself there

gusty dawn
#

I see

gusty dawn
#

mm… sorry to come back to this, but I still can't figure out the assignment portion.

inline for (std.meta.fields(T)) |ns_info| {
    // if we find the current section name
    if (std.mem.eql(u8, ns_info.name, cur_section)) {
        // @field(ret, ns_info.name) contains the inner struct now
        // loop over the fields of the inner struct, and check for key matches
        var innerStruct = &@field(ret_struct, ns_info.name); 
        inline for (std.meta.fields(@TypeOf(innerStruct.*))) |key_info| { // I assume this needs to be dereferenced?
            const field_name = key_info.name;
            // if we find the current key

            if (std.mem.eql(u8, field_name, key)) {
                // now we have a key match, give it the value
                const my_type = @TypeOf(@field(innerStruct, field_name));
                const conversion = try convert(my_type, value);
                if (conversion) |converted| {
                    @field(innerStruct, field_name) = converted; // this assignment still doesn't work
                }
            }
        }
    }
}

the assignments don't get retained, basically

#

I guess I could just In-line the calls to field.

tiny basin
#

// this assignment still doesn't work
That's not enough for me to help, alas.

gusty dawn
#

Fair

gusty dawn
#

For the anecdote, I found the issue: when calling parser.next(), the returned buffer was being re-used at every next() call.
So, checks std.mem.eql(u8, ns_info.name, cur_section) were failing because the contents of the buffer were being swapped between calls.

Since I was assigning the next() to a variable, it was just copying a pointer instead of the contents of the slice.
I had to copy the contents of the slice to avoid the pitfall.

while (try parser.*.next()) |record| {
  switch (record) {
      .section => |heading| {
          cur_section.clearRetainingCapacity();
          try cur_section.appendSlice(heading); // copy the slice
      },
      .property => |kv| {
          const key = kv.key;
          const value = kv.value;
          inline for (std.meta.fields(T)) |ns_info| {
              if (std.mem.eql(u8, ns_info.name, cur_section.items)) { // now it works!

#

I had assumed that the slice would get copied upon assignment, but it looks like what was being copied was just the pointer to the slice.