"comptime struct + method injection"

I am trying to do some “comptime struct injection”.
Of course it does not compile.

error: unable to resolve comptime value var mg = Gen(Storage).init(&storage);

I know it must be possible.
The idea is that I can put in different mechanisms to store outside of Gen.

const Storage = struct {
   fn store(self: *Storage) void  {
        _ = self;
        std.debug.print("hello", .{});
   }
};

fn Gen(comptime S: type) type {
    return struct  {
        const Self = @This();
        const StorageType = @TypeOf(S);
        storage: *StorageType,

        fn init(storage: *StorageType) Self {
            return Self {
                .storage = storage,
            };
        }

        fn store(self: *Self) void {
            self.storage.store();
        }
    };
}

fn test_storage() void {
    var storage = Storage {};
    var mg = Gen(Storage).init(&storage);
    mg.store();
}

S is already a type, const StorageType = S; is enough.

OMG Thanks it works.
How on earth does Zig know that struct function call in “store” :slight_smile:

const Storage = struct
{
   count: usize = 0,
   fn store(self: *Storage) void
   {
        self.count += 1;
        std.debug.print("hello {}, ", .{self.count});
   }
};

fn Gen(comptime S: type) type
{
    return struct
    {
        const Self = @This();

        storage: *S,

        fn init(storage: *S) Self
        {
            return Self
            {
                .storage = storage,
            };
        }

        fn store(self: *Self) void
        {
            self.storage.store();
        }
    };
}

fn test_storage() void
{
    var storage = Storage {};
    var mg = Gen(Storage).init(&storage);
    mg.store();
    mg.store();
    mg.store();
}

I don’t actually understand how any of this could work without making the functions pub.

Does it?

This was a local example. (just testing in my main.zig)
Let me check what happens if I put in something without the pub method from the outside…

src\movgen.zig:97:25: error: 'reset' is not marked 'pub'
            self.storage.reset();
            ~~~~~~~~~~~~^~~~~~
src\main.zig:301:5: note: declared here
    fn reset(self: *MoveCounter) void
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src\movgen.zig:412:25: error: 'store' is not marked 'pub'
            self.storage.store(&m);
            ~~~~~~~~~~~~^~~~~~
src\main.zig:307:5: note: declared here
    fn store(self: *MoveCounter, move: *const EngineMove) void
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src\movgen.zig:412:25: error: 'store' is not marked 'pub'
            self.storage.store(&m);
            ~~~~~~~~~~~~^~~~~~
src\main.zig:307:5: note: declared here
    fn store(self: *MoveCounter, move: *const EngineMove) void

When made pub it works, otherwise not

1 Like

So… apart from the unreadability of it, it is very powerful.
I can just inject an “interface”

Language Reference: import

Declarations which have the pub keyword may be referenced from a different source file than the one they are declared in.

Within a single file everything is accessible.

Within a single container scope actually. The sample was crossing container boundaries, and as we saw, that you can’t do.

Well as far as I can see it works always if the used functions are public.
The weird thing is, when staring at the code which is using the ‘injected storage’, you have no clue what it is, where it is etc. You cannot see references, implementations etc. like in C#.

This just works:

const std = @import("std");

const Storage = struct {
    count: usize = 0,
    fn store(self: *Storage) void {
        self.count += 1;
        std.debug.print("hello {}, ", .{self.count});
    }
};

fn Gen(comptime S: type) type {
    return struct {
        const Self = @This();

        storage: *S,

        fn init(storage: *S) Self {
            return Self{
                .storage = storage,
            };
        }

        fn store(self: *Self) void {
            self.storage.store();
        }
    };
}

fn test_storage() void {
    var storage = Storage{};
    var mg = Gen(Storage).init(&storage);
    mg.store();
    mg.store();
    mg.store();
}

pub fn main() !void {
    test_storage();
}

The only pub that is needed is for the main function, as soon as you separate pieces of the code across multiple files you need pub.

2 Likes

Yes, exactly

Within Gen storage can be an arbitrary type, so Zls can’t tell you what methods/fields it has. But once you have an instance of Gen(Storage), Zls shows completion for that:

Yes but not outside that scope, where it is abstract.
image

I don’t understand the context of your snippet, where it doesn’t work, can you provide a more full example and where the completion doesn’t work?

if you put Storage in storage.zig (with pub functions)
and Gen in gen.zig
then in the gen.zig fn store() you get no code completion.

(edit: which seems logical to me because we have no ‘interface’)

1 Like

Yes I think that is expected behavior, within Gen S is only some type and we don’t know more.

2 posts were split to a new topic: Pub, files, and container types