How to determine the comptime default value of a public decleration of a struct which's type is `type`?

const SomeErrorType = error{NewError};
const SomeStruct = struct {
    pub const variable1: type = std.io.AnyReader.Error;
    pub const variable2: type = SomeErrorType;
};

say if I have something like this… i would like to turn these into a struct field… something like the following…?

fn WrappedStruct(comptime in_type: type) type {
    const type_info = @typeInfo(in_type);
    if (type_info != .Struct) {
        @compileError("expected struct argument, found " ++ @typeName(in_type));
    }

    if (type_info.Struct.layout != .Auto) {
        @compileError("expected the container layout to be auto, found " ++ @tagName(type_info.Struct.layout));
    }

    const struct_info = type_info.Struct;
    const fields_info = struct_info.fields;

    comptime var fields: [struct_info.decls.len + fields_info.len]std.builtin.Type.StructField = undefined;
    comptime var fields_idx = 0;

    inline for (fields_info) |field| {
        fields[fields_idx] = field;
        fields_idx += 1;
    }

    inline for (struct_info.decls) |decl| {
        const decl_as_field = @field(in_type, decl.name);
        const decl_type = @TypeOf(decl_as_field);
        const decl_type_info = @typeInfo(decl_type);
        if (decl_type_info == .Fn) {
            /* get wrapped function */;
        } else {
            fields[fields_idx] = .{
                .name = decl.name,
                .type = decl_type,
                .default_value = null, /* what should it be? */
                .is_comptime = true,
                .alignment = 0,
            };
        }

        fields_idx += 1;
    }

    // return custom struct type
    return struct {};
}

what’s the x problem? well i just wanted to wrap all the public functions of a struct type into custom functions, say if SomeStruct has function foo(self: @This(), var: u32) void, would like to create a new custom Struct type which will still contain all the fields and public declarations and also function called foo(self: @This(), var: u32) void except this function’s body would be something like:

// inline if possible
pub fn foo(self: Internal, var: u32) void {
    /* does something custom */
    return self.instance.foor(var);
}
// `Internal` should be just a be a wrapped struct to save the instance of the provided type.

and the user should be able to call it like this:

const WrappedSomeStruct = WrappedStruct(SomeStruct);
const wrappedSomeStruct = WrappedSomeStruct{};
wrappedSomeStruct.foo(); // this should call the custom wrapper function
// I would also like to expose all the public declaration `type`s which aren't a function.

Since I couldn’t find a way to create custom declaration based on comptime argument, so basically converting all the declaration into comptime Struct field.

For function member variables, you can do this with some modifications to match your scenario. I’m using a directly defined function, but you can do something similar with field extraction.

pub fn foo() void {
    std.debug.print("\nCalled Foo\n", .{});
}

fn MakeFoo() type {

    comptime var fields: [1]std.builtin.Type.StructField = undefined;
            
    fields[0] = .{
        .name = "foo",
        .type = @TypeOf(foo),
        .default_value = foo, // use the actual function here
        .is_comptime = true,
        .alignment = 0,
    };

    return @Type(.{
        .Struct = .{
            .layout = .Auto,
            .fields = fields[0..],
            .decls = &.{},
            .is_tuple = false,
            .backing_integer = null
        },
    });
}

pub fn main() !void {

    var y: MakeFoo() = .{ };

    y.foo();
}

For some extended thoughts, consider the folowing…

There’s some tricks with usingnamespace that you may be interested in. It’s particularly powerful if you use it in conjunction with anytype arguments.

const WrapFoo = struct {
    pub fn foo(_: anytype) void {
        std.debug.print("\nCalled Foo\n", .{});
    }
};

const MyType = struct {
    pub usingnamespace WrapFoo;
};

pub fn main() !void {

    // call foo with whatever you want
    MyType.foo(opaque{});

    // anytype gets deduced as `@This()`
    const x: MyType = .{};

    x.foo();
}

So you can inject information from a variable into the anytype because it essentially gets called as foo(&x), However, public function declarations can get imported directly into your base type. So you can inject declarations into other structs. Spooky, right? I encourage you to mess around with that to see if you can get something closely approximating what you’re looking for.


So that aside, onto this issue about default arguments…

The issue here is that we don’t have a value to pass in for the compiler to reference when it’s building your program. What should an an i32 be defaulted to, for instance? 0? 42?

fn DefaultDemo() type {

    comptime var fields: [1]std.builtin.Type.StructField = undefined;

    // value of x is safe to take pointers to
    const x: i32 = 42;
            
    fields[0] = .{
        .name = "x",
        .type = i32,
        .default_value = &x, // set default to x
        .is_comptime = true,
        .alignment = 0,
    };

    return @Type(.{
        .Struct = .{
            .layout = .Auto,
            .fields = fields[0..],
            .decls = &.{},
            .is_tuple = false,
            .backing_integer = null
        },
    });
}

pub fn main() !void {

    const y: DefaultDemo() = .{ };

    std.debug.print("\n{}\n", .{ y.x });
}

So a comptime known value can be used as a sensible default. In your case, if we’re talking about functions, just use the actual function itself as the default value.

I hope I understood your question correctly. Either way, those are some thoughts :slight_smile:

1 Like

ah my bad, so the thing i wanted to do is something like this:

const SomeStruct = struct {
    pub fn foo(self: @This(), arg1: u32) void {
        std.debug.print("SomeStruct Foot\n", .{arg1});
    }
};

fn Wrap(comptime T: type) type {
    const struct_info = type_info.Struct;
    const fields_info = struct_info.fields;

    comptime var fields: [struct_info.decls.len + fields_info.len]std.builtin.Type.StructField = undefined;
    comptime var fields_idx = 0;

    inline for (fields_info) |field| {
        fields[fields_idx] = field;
        fields_idx += 1;
    }

    inline for (struct_info.decls) |decl| {
        const decl_as_field = @field(in_type, decl.name);
        const decl_type = @TypeOf(decl_as_field);
        const decl_type_info = @typeInfo(decl_type);

        if (decl_type_info == .Fn) {
            fields[fields_idx] = .{
                .name = decl.name,
                .type = decl_type,
                .default_value = struct {
                    pub fn wrapper_foo(/* @paramsOf(decl_as_field) the fn */) {
                        std.debug.print("Wrapper foo was called\n", .{});
                        @call(.{.auto}, decl_as_field, @paramsOf(decl_as_field));
                    }
                }.wrapper_foo,
                .is_comptime = true,
                .alignment = 0,
            };
        }

        fields_idx += 1;
    }

    return @Type(.{
        .Struct = .{
            .layout = .Auto,
            .fields = fields[0..],
            .decls = &.{},
            .is_tuple = false,
            .backing_integer = null
        },
    });
}

fn main() void {
    const WrappedSomeStruct = Wrap(SomeStruct);
    const instance = WrappedSomeStruct{};
    instance.foo();
}

does this makes somewhat sense now? and the wrapper function should be same for all other functions in my case i guess :slight_smile:

Yes, actually, I understand. I’ll give you something similar that I’ve built for constructing callbacks. First, let’s handle your example. I’m not building the full thing but I want to give you enough information to be helpful. Let’s build a wrapper that returns a struct.

I’m assuming a stripped down version for functions that return void and take a tuple as an argument:

const std = @import("std");

const builtin = @import("builtin");

fn FunctionWrapper(func: anytype) type {
    return struct {
        // anytype makes it a member function
        pub fn call(args: anytype) void {
            std.debug.print("\nHello, Wrapper!\n", .{});
            @call(.always_inline, func, args);
        }  
    };
    
}


fn WrapperStruct(func: anytype) type {

    comptime var fields: [1]std.builtin.Type.StructField = undefined;

    const wrapped_call = FunctionWrapper(func).call;
            
    fields[0] = .{
        .name = "whatever",
        .type = @TypeOf(wrapped_call),
        .default_value = wrapped_call, 
        .is_comptime = true,
        .alignment = 0,
    };

    return @Type(.{
        .Struct = .{
            .layout = .Auto,
            .fields = fields[0..],
            .decls = &.{},
            .is_tuple = false,
            .backing_integer = null
        },
    });
}

pub fn foo() void {
    std.debug.print("\nGoodbye, Wrapper!\n", .{});
}

pub fn main() !void {
    
    const wrapped = WrapperStruct(foo){ };

    wrapped.whatever(.{});
}

Now, I’d really like to experiment with some find of a wrapper function that does exact call-through using this mechanism:

fn WrapperStruct(func: anytype) type {

    const info = @typeInfo(@TypeOf(func)).Fn;

    const func = @Type(.{
        .Fn = .{
            .calling_convention = info.calling_convention,
            .alignment = info.alignment,
            .is_generic = info.is_generic,
            .is_var_args = info.is_var_args,
            .return_type = info.return_type,
            .params = info.params
        }
    });
    
    ...

It’s totally redundant for how I have it setup there… but I’ll need to do some experimenting to see how to link the function body here :slight_smile:

1 Like

could we just generate raw tokens directy like rust macros or prehaps something like the asm compiler-keyword block.

Yes, and it’s a very interesting problem now that I’m looking at it.

The thing with inline assembly is yes… you can do anything but is it portable? At that point, we’ve ventured outside of Zig by quite a bit.

The problem is we can generate type information, but you cannot generate the function body by any inbound means that I’m aware of.

So you’d generate the function arguments… and then you’d have to find a way to also pass those arguments to the function that you’re calling. You have two issues there, really.

The only approach I can think of would probably suck, tbh… but it would go like this…

You’d make a variadic c function that can assign to tuple members from a struct instance that gets provided by the “self” argument which then gets put into a @call. Ouch. I hate it, lol. And this is all a guess - it probably doesn’t work like that.

That said, I have provided one way of doing it that at least works with tuples. If we wanted to open this up to a “brainstorming” topic, we could fuss with it more there.

1 Like

ah yes it’s indeed an interesting, let’s move to brainstorming category, any suggestion for the title or prehaps you could start it i will probably miss-phrase some part at this point haha :slight_smile:

Great question… I would probably call it “Generating Functions from Type Information” or something like that.