How to define and use a function as parameter?

I want to pass the response body handle in the code below as a parameter, how can I do it ?

fn http_get(client: *http.Client, uri: Uri, headers: http.Headers, allocator: mem.Allocator, response_body_max_len: usize, comptime T: type) !json.Parsed(T) {
    // init request
    var req = try client.request(.GET, uri, headers, .{});
    defer req.deinit();

    // send request
    try req.start();

    // wait response
    try req.wait();

    // get response
    var res = req.response;
    // TODO: body handle parameterize, e.g. a param: fn(T, allocator, response, io_reader) !T
    return switch (res.status) {
        // handle ok response
        .ok => try readJsonAs(T, allocator, req.reader(), response_body_max_len),
        else => error.HttpStatusNotOk,
    };
}

Hello @aiac! Welcome to the forum.

Please check out this related thread: Anonymous functions/lambdas - Help - Ziggit

It has several examples of what I believe you are looking for. If you still have questions, feel free to follow up!

– edit

I wanted to post a few more examples because the relation between these two threads may be a bit cryptic at a first glance. You can easily use the “anytype” parameter type as a way to generically take in functions as compile time arguments. (Please note that you can take in function pointers as well, the example that I am giving here is comptime oriented, but you can deduce the type of function pointers as well).

Here we have a dispatching function that takes in a binary function as a first parameter:

fn arithmeticDispatch(    
    comptime BinaryFunc: anytype, 
    x: anytype,
    y: anytype,
    z: anytype,
) void { ...

Here we have a generic function that can be passed to that parameter:

inline fn addGeneric(x: anytype, y: anytype) @TypeOf(x) {
    return x + y;
}

Finally, here we have the dispatching call being made that binds the two:

arithmeticDispatch(addGeneric, x, y, z);

Another parameter declaration pattern from that same thread was made by @krilcebre (with @dude_the_builder 's edit):

pub fn all(comptime T:type, buf: []const T, comptime predicate: (fn (val: T) bool)) bool {
    return for (buf) |val| {
        if (!predicate(val)) break false;
    } else true;
}

// ...

all(i32, arr_1[0..], struct {
    pub fn eql(val: i32) bool { return val == 1; }
}.eql)

The take-away from that last example is the (fn (val: T) bool) as the predicate argument in the parent function “all”

4 Likes

Thank you for your help, I modified my code, then found a new compilation error. Where did I write it wrong?

error: function prototype may not have inferred error set
    comptime response_body_handle: fn(T:type, allocator: mem.Allocator, response: http.Client.Response, io_reader: anytype) !jso
n.Parsed(T)

code:

fn http_get(
    client: *http.Client,
    uri: Uri,
    headers: http.Headers,
    allocator: mem.Allocator,
    comptime T:type,
    comptime response_body_handle: fn(T:type, allocator: mem.Allocator, response: http.Client.Response, io_reader: anytype) !json.Parsed(T) 
) !json.Parsed(T) {
    // init request
    var req = try client.request(.GET, uri, headers, .{});
    defer req.deinit();

    // send request
    try req.start();

    // wait response
    try req.wait();

    // get response
    var res = req.response;

    return try response_body_handle(T,allocator,res,req.reader());
}

// call 
var parsed = try http_get(&client,uri,headers,allocator, PokemonList(), struct {
    pub fn handle(comptime T:type, _allocator: mem.Allocator, response: http.Client.Response, io_reader: anytype) !json.Parsed(T) {
        return switch (response.status) {
            // handle ok response
            .ok => try readJsonAs(T, _allocator, io_reader, response_body_max_len),
            else => error.HttpStatusNotOk,
        };
    }
}.handle);
defer parsed.deinit();

Yup, so the inferred error is your issue here: https://ziglang.org/documentation/master/#Inferred-Error-Sets

The inferred error is coming from the non-explicitly specified error on the return type - note that you have:

!json.Parsed(T)

The exclamation mark is inferring what the error is. Here’s an example of a non-inferred error (from the documentation):

const A = error{
    NotDir,

    /// A doc comment
    PathNotFound,
};
const B = error{
    OutOfMemory,

    /// B doc comment
    PathNotFound,
};

const C = A || B;

fn foo() C!void {
    return error.NotDir;
}

Note that the function “foo” returns a “C!void” where C is a combination of error types A and B (A || B).

That is non-inferred because it is explicitly stated what the errors it can return are. In this case, you are returning an error.HttpStatusNotOk.

3 Likes

You are really awesome, I solved the problem very quickly, thank you very much :blush:

I temporarily used anyerror! instead of !

But I still don’t understand the difference

2 Likes

Glad I could help :slight_smile:

The difference really is that you may actually want to restrict the kinds of functions that people pass as a parameter because perhaps it interacts with other components that expect a specific set of error codes for various reasons.

Also, a part of the reasoning behind this is could be that there are no errors to infer from what is purely a declaration so this mechanism can’t really be deduced here. This is speculation on my part, however.

1 Like