How does Zig implement closures to capture environment variables

I’m using zig to implement a genetic algorithm for the TSP problem. How can func capture the distmat environment variable while keeping the GA structure and inner_func unchanged?

const std = @import("std");

const Gene = []usize;

const GA = struct {
    inner_func: *const fn (Gene) f64,

    pub fn init(inner_func: *const fn (Gene) f64) GA {
        return .{ .inner_func = inner_func };
    }

    pub fn evolve(self: *GA) void {
        var gene: [4]usize = .{ 1, 2, 3, 4 };
        const fitness = self.inner_func(&gene);
        std.debug.print("{d}\n", .{fitness});
    }
};

fn func(gene: Gene) f64 {
    var distance: f64 = 0.0;
    for (gene) |i| {
        const dis: f64 = @floatFromInt(i);
        distance += dis;
    }
    return 1.0 / distance;
}

pub fn main() void {
    var ga = GA.init(func);
    ga.evolve();
}

I want to achieve the following effect without using global variables, because I will be using multiple threads to solve multiple problems in the future, and the environment variables captured in func are not limited to dismat, other parameters may be added later

pub fn main() void {
    var distmap: [4][4]usize = .{
        .{ 1, 2, 3, 4 },
        .{ 1, 2, 3, 4 },
        .{ 1, 2, 3, 4 },
        .{ 1, 2, 3, 4 },
    };

    fn func(gene: Gene) f64 {
        var distance: f64 = 0.0;
        for (1..gene.len) |i| {
            const dis: f64 = dismat[gene[i-1]][gene[i]];
            distance += dis;
        }
        return 1.0 / distance;
    }

    var ga = GA.init(func);
    ga.evolve();
}

This feature can be easily implemented in both Rust and Python

There is no support for closures in Zig.

You can work around this by using structures with one execute function.

const Func = struct {
    distmap: [4][4]usize = .{
        .{ 1, 2, 3, 4 },
        .{ 1, 2, 3, 4 },
        .{ 1, 2, 3, 4 },
        .{ 1, 2, 3, 4 },
    },
    fn execute(self: Func, gene: Gene) f64 {
        // replace distmap with self.distmap
        var distance: f64 = 0.0;
        for (1..gene.len) |i| {
            const dis: f64 = self.dismat[gene[i-1]][gene[i]];
            distance += dis;
        }
        return 1.0 / distance;
    }
};
var ga = GA.init(Func{});
ga.evolve();

You also need to change GA inner_func type and init to accept anytype and call execute.

1 Like

Here’s a hint: use of undeclared identifier ‘Func’
fn execute(self: Func, gene: Gene) f64

And Func must be defined in a function to meet the requirements

The following two operations are also not allowed, prompting that inner_func must be of a known type at compile time

const GA = struct {
    inner_func: anytype,
}

fn GA(Func: anytype) type {
    return struct {
        inner_func: Func,

        pub fn evolve(self: *GA) void {
            var gene: [4]usize = .{ 1, 2, 3, 4 };
            const fitness = self.inner_func(&gene);
            std.debug.print("{d}\n", .{fitness});
        }
    };
}

The current implementation is as follows, zls does not warn, but the compilation does not pass

const std = @import("std");

const Gene = []usize;

fn GA(Func: anytype) type {
    return struct {
        func: Func,

        pub fn evolve(self: *GA) void {
            var gene: [4]usize = .{ 1, 2, 3, 4 };
            const fitness = self.func.execute(&gene);
            std.debug.print("{d}\n", .{fitness});
        }
    };
}

pub fn main() void {
    const MyFunc = struct {
        const A = @This();
        distmap: [4][4]usize = .{
            .{ 1, 2, 3, 4 },
            .{ 1, 2, 3, 4 },
            .{ 1, 2, 3, 4 },
            .{ 1, 2, 3, 4 },
        },

        fn execute(self: A, gene: Gene) f64 {
            var distance: f64 = 0.0;
            for (1..gene.len) |i| {
                const dis: f64 = self.distmap[gene[i - 1]][gene[i]];
                distance += dis;
            }
            return 1.0 / distance;
        }
    };

    var ga = GA(MyFunc{});
    ga.evolve();
}

The error message is as follows:

test.zig:37:9: error: variable of type 'type' must be const or comptime
    var ga = GA(MyFunc{});
        ^~
test.zig:37:9: note: types are not available at runtime

The simplest way is:

const std = @import("std");

const Gene = []usize;

const GA = struct {
    pub fn evolve(closure: anytype) void {
        var gene: [4]usize = .{ 1, 2, 3, 4 };
        const fitness = closure.execute(&gene);
        std.debug.print("{d}\n", .{fitness});
    }
};

pub fn main() void {
    const MyFunc = struct {
        const A = @This();
        distmap: [4][4]usize = .{
            .{ 1, 2, 3, 4 },
            .{ 1, 2, 3, 4 },
            .{ 1, 2, 3, 4 },
            .{ 1, 2, 3, 4 },
        },

        fn execute(self: A, gene: Gene) f64 {
            var distance: f64 = 0.0;
            for (1..gene.len) |i| {
                const dis: f64 = self.distmap[gene[i - 1]][gene[i]];
                distance += dis;
            }
            return 1.0 / distance;
        }
    };

    GA.evolve(MyFunc{});
}
1 Like

This will do, thank you very much!! Let me use this method to try to refactor my code

2 Likes