#@OffsetOf() with comptime-generated field names

1 messages · Page 1 of 1 (latest)

trail sail
#

Hey all, first time poster! Hoping y'all might be able to help with this comptime question.

I'm hoping to build a node-based audio system where users can bring their own processing structs and easily integrate them with other processing nodes, provided they stick to a few rules.

To that end, I'd like to provide a uniform way to access fields of arbitrary structs, where these fields are acting as inputs and outputs for node processing.

My general idea has been to check structs for field names that start with "in" and "out", and build a function that collates pointers to these fields for instances of these structs.

so, given some struct like:

const node: Node = .{
  .in_one = "1",
  .in_two = "2",
  .in_three = "3",
  .drive_in = "no",
  .out_back = "yes",
  .steak_house = "yum",
};

I could build some struct that could take this Node type, pull the field names in_one , in_two, and in_three by way of std.meta.fields(Node), and then build a function that can find the pointer offsets of these fields for an instance of Node at runtime, such that you'd get a tidy array holding pointers to your "in" fields.

I'm coming up against an issue where my comptime-built array of field names isn't playing nice with offsetOf(Node, name) at runtime, yielding the error:

no offset available for comptime field
                        const offset = @offsetOf(T, name);

Would appreciate any advice/pointers!

tulip nest
#

No advice to give -- interesting idea to wire things by convention at comptime, instead of something more OOPy with runtime query for connection points and a common (named) connection structure. Of course, this kind of scheme that exposes names of code things to users isn't friendly to localizing the software.

nocturne harness
open cipher
#

Can you show us the definition of Node?

#

no offset available for comptime field hints that one of your fields is comptime only.

#

So, it has a value but it's immutable and isn't actually stored in memory in the struct.

#

You probably only want to be getting the offsets of fields where StructField.is_comptime is false.

craggy adder
#

you might be able to achieve this with std.meta.FieldEnum(T). something like this

const std = @import("std");

pub const S = struct {
    a: u8,
    b: bool,
    c: u8,
};

test {
    const ins = [_]std.meta.FieldEnum(S){ .a, .b };
    var s: S = undefined;
    for (ins) |in| {
        switch (in) {
            inline else => |tag| std.debug.print("in {s} {*}\n", .{ @tagName(in), &@field(s, @tagName(tag)) }),
        }
    }
    const outs = [_]std.meta.FieldEnum(S){ .b, .c };
    for (outs) |out| {
        switch (out) {
            inline else => |tag| std.debug.print("out {s} {*}\n", .{ @tagName(out), &@field(s, @tagName(tag)) }),
        }
    }
}

EDIT
here i'm using the FieldEnum(S)'s tag name to get runtime pointers into s

trail sail
#

Sure thing!

The comptime struct im building looks something like this:

   fn Ports(comptime T: anytype) type {
        const num_in_ports = getNumPorts(T, .in);
        const num_out_ports = getNumPorts(T, .out);

        const S = Context.Signal(f32);

        const inlet_names: [num_in_ports][]const u8 = comptime blk: {
            var names: [num_in_ports][]const u8 = .{undefined} ** num_in_ports;
            const fields = std.meta.fields(T);
            var idx: usize = 0;

            for (fields) |f| {
                const name: []const u8 = f.name;
                if (std.mem.startsWith(u8, name, "in")) {
                    names[idx] = name;
                    idx += 1;
                }
            }

            break :blk names;
        };

        return struct {
            in: [num_in_ports]*?S = undefined,
            out: [num_out_ports]*?S = undefined,

            pub fn init(n: *const T) @This() {
                const in = blk: {
                    var result: [num_in_ports]*?S = .{undefined} ** num_in_ports;

                    inline for (inlet_names, 0..) |name, idx| {
                        const offset = @offsetOf(T, name);
                        result[idx] = @ptrFromInt(@intFromPtr(n) + offset);
                    }

                    break :blk result;
                };

                return .{ .in = in, .out = .{undefined} ** num_out_ports };
            }

            fn outlet_offsets() void {
                std.debug.print("outlets!", .{});
                return;
            }
        };
    }

#

As for the Node type definition in my initial example, was mostly arbitrary: I'm hoping it doesn't matter what the shape of it is. I've been testing with the anonymous struct above like so:

    test "Ports" {
        const node = .{
            .in_one = "1",
            .in_two = "2",
            .in_three = "3",
            .drive_in = "no",
            .out_back = "yes",
            .steak_house = "yum",
        };

        const p = Ports(@TypeOf(node)).init(&node);

        std.debug.print("ins: {any}", .{p.in});
    }

#

(meta question, what should I be using for code markup to get the appropriate highlighting? I've been trying "zig", but no dice

drifting zodiac
#

rs works decently

craggy adder
#

```ts or rs works well

open cipher
#

I've been testing with the anonymous struct above like so
This is why you're getting an erorr.

#

Those anonymous struct's fields are comptime.

#
const std = @import("std");

pub fn main() void {
    var a: []const u8 = "foo";
    _ = &a;
    var b = .{
        .a = a,
        .c = "bar",
    };
    b.a = "baz";
    //b.c = "baz"; // causes error: value stored in comptime field does not match the default value of the field
    std.debug.print("{any}\n", .{b});
    // Prints: struct{a: []const u8, comptime c: *const [3:0]u8 = "bar"}{ .a = { 98, 97, 122 }, .c = { 98, 97, 114 } }
}
#

See how c is comptime while a isn't.

#

In your test, you should just write a Node struct.

trail sail
#

Great call! I just tried it with a proper struct and it worked, thanks so much for the help!

trail sail
craggy adder
# trail sail Sure thing! The comptime struct im building looks something like this: ```ts ...

expanding on the code i shared before, i might write something like this to that end:

const std = @import("std");

fn Ports(
    comptime T: type,
    comptime ins: []const std.meta.FieldEnum(T),
    comptime outs: []const std.meta.FieldEnum(T),
) type {
    return struct {
        t: *T,

        const FE = std.meta.FieldEnum(T);

        pub fn init(t: *T) @This() {
            return .{ .t = t };
        }

        pub fn getIn(self: @This(), comptime idx: usize) *std.meta.FieldType(T, ins[idx]) {
            return &@field(self.t, @tagName(ins[idx]));
        }
        pub fn getOut(self: @This(), comptime idx: usize) *std.meta.FieldType(T, outs[idx]) {
            return &@field(self.t, @tagName(outs[idx]));
        }
    };
}

pub const S = struct {
    a: u8,
    b: bool,
    c: u8,
};

test {
    var s: S = undefined;
    const p = Ports(S, &.{ .a, .b }, &.{ .b, .c }).init(&s);

    std.debug.print("{*}\n{*}\n{*}\n", .{
        p.getIn(0),
        p.getIn(1),
        p.getOut(0),
    });
}
#

not sure if it works for your use case but maybe this is interesting

#
$ zig test /tmp/tmp.zig
u8@7ffd2368c855
bool@7ffd2368c856
bool@7ffd2368c856
#

or if you want something more general for getting a field pointer such as getPtr(.c),:

        pub fn getPtr(self: @This(), comptime fe: FE) *std.meta.FieldType(T, fe) {
            return &@field(self.t, @tagName(fe));
        }
trail sail
#

ooh, there's quite a bit going on in these examples

craggy adder
#

let me know if you have any questions.

trail sail
#

Most definitely! Appreciate the help, gonna do some reading up on some of these builtins and get back to you sometime.

craggy adder
#

sounds good. @field and @tagName are the main ones i guess.

#

std.meta.FieldEnum(T) makes an enum type with a tag for each field in a struct or union

#

and meta.FieldType(T, meta.FieldEnum(T)) gives you a field's type

trail sail
#

going the FieldType/FieldEnum route offers much more flexibility wrt typing, so I definitely see myself going that route

craggy adder
#

a slight modification where ins and outs are stored within the Node (previously just named S) decl https://zigbin.io/9222cb

#

i think its a little cleaner with only a single type param, T