Function pointer loses const in for loop

I’m building an array of a struct that contains a const function pointer, but I’m unable to call the function pointers since they’re losing const when called from the body of a for loop. If I call into the array directly (e.g. actions[0].func.*()) then this code works as expected.

Why does the for loop here cause my pointers to lose const?

The smallest example I could make of the error is this:

const std = @import("std");

const Action = struct {
    name: []const u8,
    func: *const fn () anyerror!void,

const actions = [_]Action{
    .{ .name = "foo", .func = &foo },
    .{ .name = "bar", .func = &bar },

pub fn main() !void {
    for (0..actions.len) |i| {
        std.debug.print("{s}: }", .{ actions[i].name });
        try actions[i].func.*();

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

pub fn bar() !void {
    std.debug.print("hello from bar\n", .{});

And the compiler error is this:

example.zig:16:28: error: values of type 'fn () anyerror!void' must be comptime-known, but operand value is runtime-known
example.zig:16:28: note: use '*const fn () anyerror!void' for a function pointer type

I also have a godbolt link, if that’s helpful: Compiler Explorer

The function pointer dereference is unnecessary.

try actions[i].func();

And Describing single close-wave-bracket result in compiling error.
If you want to describe single close-wave-bracket, you can use adjacent close-wave-bracket.

std.debug.print("{s}: }}", .{ actions[i].name });

see also Call2Op example in Documentation - The Zig Programming Language


You can also iterate directly on arrays without an index:

pub fn main() !void {
    for (actions) |action| {
        std.debug.print("{s}: }", .{ });
        try action.func();

A little side-question here: Does Zig do parameter reference optimization (PRO) if the array is really big? So it would behind the scenes iterate over a *const to the array instead?

Last time I checked zig copied each array element. To avoid memcpy you have to explicitly write:

    for (&actions) |*action| {

see also: How to avoid implicit memcpys?


I think you meant:

for (&actions) |*action| {

Oh, that’s super cool. Thank you! I’m mostly learning the language from guessing and seeing what the compiler says, so I completely missed that this feature was available.

1 Like

Yes, thank you @dude_the_builder, & is required for pointer capture.
I edited my answer with your correction.

1 Like

Not completely true.

Last time this came up, I did a bunch of testing and there were a lot of back and forth about the different loop syntaxes. Most actually generate the same code (the discussion was originally around try to force the loop to use a pointer in stead of an array index, and it was surprisingly difficult).

If the capture isn’t mutate (the compiler already has to figure this out), then it will make the capture a *const if it is larger than a register.

There is alike a two year old bug around a spurious array copy that is a huge performance killer, but I can’t find it right now.

1 Like

I keep getting the feeling that this type of optimization is not rigtht for Zig. It’s just easier to be explicit for (&array) |*const item| or for (&array) |*item| if you don’t want to copy array, and for (array) |item| if you don’t care; versus trying to do mental gymnastics as to when the compiler is going to copy or not.

I wouldnt call that an optimization - there was a long github thread over this.

for is a language construct, not a function, just like if isn’t a function or switch, and this construct (not a function optimization) happens everywhere. eg hugearray[i] doesnt make a copy. if(optional) dosn’t make a copy… etc.

The simplest and most consistent would be to just say you cannot loop over a pointer (pointer pointer, not slice pointer) and the capture always determines what is handed in. It should never make a copy of the argument inside the for() in anyway. An element wise couple would be to capture by copy and capturing by pointer would give you a pointer to the array looped over - not a copy of it.

That is the most constant and simplest. Every other you get weird cases and it is highly unintutive. Anything else makes it more complex than it needs to be. If you want to copy the entire array, this is one the cases where there is no performance benefit to having the compiler do it. (there are some cases where the implicit copy is actually useful though).

I hugely disagree with how zig does looping on almost every level and decision, but i think that ship sailed long ago.

1 Like