Unable to resolve comptime value error

I have this code:

const std = @import("std");

pub const ResourceError = error {
    FileNotFound,
    InvalidFormat,
};

pub fn ResourceLoader(comptime T: type) type {
    return struct {
        load: fn ([]const u8, std.mem.Allocator) ResourceError!*T,
        deinit: fn(self: *T, std.mem.Allocator) void,
        clone: fn(self: *T, std.mem.Allocator) *T,
        getName: fn() []const u8,
    };
}

pub const ResourceManager = struct {

    const LoaderInfo = struct {
        loader: *anyopaque,
    };

    var loaders: std.ArrayList(LoaderInfo) = undefined;

    // there's an init() function that initializes `loaders` but I left it out since it's not relevant

       pub fn addLoader(comptime T: type, loader: ResourceLoader(T)) void {

           // !!! COMPILER ERROR HERE !!!
           const copy = loaders.allocator.create(ResourceLoader(T)) catch @panic("Out of memory");
           copy.* = loader;
           loaders.append(LoaderInfo {  .loader = copy }) catch @panic("Out of memory");
       }
};

test "my test" {
    // my actual code has this, but it's not necessary for this example
    // const allocator = std.testing.allocator;
    //ResourceManager.staticInit(allocator);

    const loader = ResourceLoader(usize) {
        .clone = undefined,
        .deinit = undefined,
        .getName = undefined,
        .load = undefined,
    };

    ResourceManager.addLoader(usize, loader);
}

I’m getting an error on the loaders.allocators.create() call, saying unable to resolve comptime value, but I don’t understand why. Shouldn’t ResourceLoader(T) be comptime-known in this context?

Running the code I get the following error message (always better to include the error message instead of paraphrasing it, it has more valuable info):

alloc.zig:29:39: error: unable to resolve comptime value
        const copy = loaders.allocator.create(ResourceLoader(T)) catch @panic("Out of memory");
                     ~~~~~~~~~~~~~~~~~^~~~~~~
alloc.zig:29:39: note: argument to function being called at comptime must be comptime-known
/home/sze/development/workspace/zig/active/zig-linux-x86_64-0.12.0/lib/std/mem/Allocator.zig:103:55: note: expression is evaluated at comptime because the generic function was instantiated with a comptime-only return type
pub fn create(self: Allocator, comptime T: type) Error!*T {
                                                 ~~~~~^~~

Reading the error message you may wonder why is it being called at comptime?
The last part provides the hint:

is evaluated at comptime because the generic function was instantiated with a comptime-only return type

The type returned by ResourceLoader is a comptime only type because you use function body types, which are comptime only.
Instead you need to use function pointer types like so:

pub fn ResourceLoader(comptime T: type) type {
    return struct {
        load: *const fn ([]const u8, std.mem.Allocator) ResourceError!*T,
        deinit: *const fn (self: *T, std.mem.Allocator) void,
        clone: *const fn (self: *T, std.mem.Allocator) *T,
        getName: *const fn () []const u8,
    };
}

I think in older Zig versions you could get function pointers without the *const syntax.

2 Likes

There’s a lot here I need to wrap my head around.

The type returned by ResourceLoader is a comptime only type because you use function body types, which are comptime only.

My intent was to use function pointers inside ResourceLoader, so I guess I messed up the syntax. But what are “function body types?” Do you mean my original syntax was something closer to a C typedef instead of an actual function pointer declaration?

A bigger question is I thought ResourceLoader was supposed to be comptime-only, since it defines a new type and types are always evaluated at compile time. But when the fields become function pointers instead of “function body types” it becomes something other than comptime-only? There’s a distinction somewhere that I don’t understand.

All types are comptime only, they get compiled into the program and disappear, only names of types etc. may remain to some degree by using things like @typeName etc.

What I meant is that instances of the ResourceLoader type can only be used at comptime if it contains function body types, because values of function body type can only be used at comptime. A function body type is basically a comptime reference that identifies a particular function, you can call it and it gets compiled into the program as call of the function, but if you want to point to it at runtime, you have to take the address and turn it into a function pointer.

Under Functions there is a short mention of function body types:

There is a difference between a function body and a function pointer. Function bodies are comptime-only types while function Pointers may be runtime-known.

I think function body types are definitely under documented, I can’t really find anything that explicitly talks about them, apart from that one sentence.
But if you write a bit of comptime code and use @compileLog to print the types, you develop an intuition for how they work and how they relate to function pointers.

I think of them as a comptime handle that identifies a particular function.

I guess to say more about them you would have to dig into Zigs implementation, or ask somebody that is more familiar with the implementation details.

1 Like

I think I’m starting to get it.

In other words, ResourceLoader (the function) is indeed always comptime-only, but the actual type that it returned was also comptime-only (or whatever the term is for a type that can only be used in other comptime code), and that’s why I was getting the error. Fixing the function pointers means that ResourceLoader now returns a regular type, not a “can only use at comptime” type, and the compiler error goes away. Is that right?

1 Like

Yes, I think that describes it accurately.

1 Like