Getting function name at comptime and stringify

Is there a way to get the name of a function at comptime (not while inside the function).

I have some code that takes an array of functions of the same type and an array of tuples of runs the cross product of the sets, but I need to generate a string that makes sense for each pair so when I display the results on a chart they are easy to understand.

edit: Also is there a way to generate a string literal from a number? Something like the #define stringify(x) (#x) followed by stringify(123)?

There is no way to access the name of a function just based off of a function pointer.

You probably want to pass the names of the functions alongside the function pointers, something like:

const NamedFn = struct {
    name: []const u8,
    func: *const fn () void,
};

For your second question, you can use std.fmt to format integers:

// this is for comptime-known numbers, use `format` or `bufPrint` for runtime-known numbers
const str = std.fmt.comptimePrint("{d}", .{123});

You probably want to pass the names of the functions alongside the function pointers

That’s what I;m doing now and it sucks. It wouldn’t be a function pointer, but a function body if that matters.

The comptime fmt looks like what I want thanks

I’m not sure what you mean by “function body”, can you share the relevant code?

There is no way to stringify like in C preprocessor.
If you have a function pointer you cannot get its name, because it is a runtime pointer.
But if you have a public function in a namespace you can get the name.
If namespace T contains a pub fn

    inline for (@typeInfo(T).Struct.decls) |decl| {
        const function_name =  decl.name;
        const function = @field(T, function_name);
        if (@typeInfo(@TypeOf(function)) == .Fn) {
            // you can have a compile-time call of function here
            // @call(.auto, function, .{});
        }
    }

EDIT:
You can combine this with @n0s4 proposal, and create an array of NamedFn with a function pointer and its name.

const NamedFn = struct {
    name: []const u8,
    func: *const fn () void,
};

inline for (@typeInfo(T).Struct.decls) |decl| {
        const function_name =  decl.name;
        const function = @field(T, function_name);
        if (@typeInfo(@TypeOf(function)) == .Fn) {
            add(NamedFn{.name = function_name, .func = &function});
        }
    }
1 Like
fn by_pointer(Func: type, pointer: *const Func) void {
    pointer(); // pointer is rt known not inlineable
}

fn by_body(Func: type, body: Func) void {
    body(); // body is ct known and inlineable
}

comptime pointer: *const Func is somewhere in the middle I think.

function bodies are ct known though, no? That’s their major (only?) difference.

And I don’t have a namespace, there may be more than 1 involved even.

Yes, but as functions, not as functions pointers.

decls show up in the field list? are you sure? I didn’t know that.

Yes, they are listed, but they must be pub.

pub requirement and have to know the namespace are issues I think.

This is basically for a benchmark runner.I have in an array of function bodies (fn() void vs *const fn() void) all the same type and an array of tuples that are different arguments to the functions. I run the cross product and produce gnuplot results. That’s basically the whole thing

Right now I take in an optional list of names (same size as the function body array) as the function names and if not supplied I generate them as func[N](M) where M is a number 1 to the length of the args list (I can’t of any other way to differentiate them – I’ll write code to comptime print them, but after I’d finished the rest.

So that’s the whoile story.

(I’ll prob add an optional namespace arg and do what you did above for the times that will be enough, but I would really just like the function name from the function body arg – the compilter definitely has it at the point. would make like much easier)

Two ideas:

  1. You can do that:

const T = struct {
pub bench1 = namespace.bench1;
pub bench2 = namespace.bench2;
};

and then use T to get the names and function pointers.

  1. Another way is:

pub bench_foo() void {
…

use T = @This() and filter the function_name that starts with “bench_”

1 Like

version 1: this already known by the compiler and is just a hassle. I’ll just let the user import the defs to the local namespace (or any single namespace) and leave it at that II think.

version 2: this isnt how anything is strcuture (I thnk – not fully understanding it mayne).

This really should exist and shouldn’t be so difficult.

Here is something I am using together with my ecs to define a bunch of handler functions per component type:

handler.zig

const std = @import("std");

fn NamedFn(comptime Fn: type) type {
    return struct {
        name: []const u8,
        func: Fn,

        pub fn format(
            self: @This(),
            comptime fmt: []const u8,
            options: std.fmt.FormatOptions,
            writer: anytype,
        ) !void {
            _ = fmt;
            _ = options;
            try writer.print("{s}", .{self.name});
        }
    };
}

fn FnTable(comptime Fn: type, comptime Namespace: type) type {
    const N = NamedFn(Fn);
    const info = @typeInfo(Namespace);
    const decls = info.Struct.decls;
    var fields: [decls.len]std.builtin.Type.StructField = undefined;
    for (decls, 0..) |d, i| {
        fields[i] = .{
            .name = d.name,
            .type = N,
            .default_value = &N{ .name = d.name, .func = @field(Namespace, d.name) },
            .alignment = @alignOf(N),
            .is_comptime = true,
        };
    }
    return @Type(.{
        .Struct = .{
            .layout = .auto,
            .fields = &fields,
            .decls = &.{},
            .is_tuple = false,
        },
    });
}

pub fn Table(comptime _Fn: type, comptime Namespace: type) type {
    return struct {
        pub const Fn = _Fn;
        pub const Named = NamedFn(_Fn);
        pub const fns: FnTable(_Fn, Namespace) = .{};
    };
}

Usage:

const handler = @import("handler.zig");

pub const FormattingError = error{OutOfMemory};
pub const ComponentToStringFn =
    *const fn (c: *Context, allocator: std.mem.Allocator, value: *anyopaque) FormattingError![:0]const u8;

pub const Format = handler.Table(ComponentToStringFn, struct {
    pub fn string(c: *Context, allocator: std.mem.Allocator, ptr: *anyopaque) FormattingError![:0]const u8 {
        _ = c;
        return allocator.dupeZ(u8, ptrToValue([]const u8, ptr));
    }
    pub fn sound(c: *Context, allocator: std.mem.Allocator, ptr: *anyopaque) FormattingError![:0]const u8 {
        _ = c;
        _ = allocator;
        const s = ptrToValue(ray.Sound, ptr);
        return if (ray.IsSoundPlaying(s)) "<sound playing>" else "<sound>";
    }
    pub fn music(_: *Context, _: std.mem.Allocator, _: *anyopaque) FormattingError![:0]const u8 {
        return "<music>";
    }
    pub fn vec2(c: *Context, allocator: std.mem.Allocator, ptr: *anyopaque) FormattingError![:0]const u8 {
        _ = c;
        const v = ptrToValue(Vec2, ptr);
        return std.fmt.allocPrintZ(allocator, "{{ {d:.2}, {d:.2} }}", .{ v[0], v[1] });
    }
    pub fn vec3(c: *Context, allocator: std.mem.Allocator, ptr: *anyopaque) FormattingError![:0]const u8 {
        _ = c;
        const v = ptrToValue(Vec3, ptr);
        return std.fmt.allocPrintZ(allocator, "{{ {d:.2}, {d:.2}, {d:.2} }}", .{ v[0], v[1], v[2] });
    }
    pub fn value_bool(c: *Context, allocator: std.mem.Allocator, ptr: *anyopaque) FormattingError![:0]const u8 {
        _ = c;
        _ = allocator;
        return if (ptrToValue(bool, ptr)) "true" else "false";
    }
    pub fn value_u32(c: *Context, allocator: std.mem.Allocator, ptr: *anyopaque) FormattingError![:0]const u8 {
        _ = c;
        return std.fmt.allocPrintZ(allocator, "{d}", .{ptrToValue(u32, ptr)});
    }
    pub fn value_i32(c: *Context, allocator: std.mem.Allocator, ptr: *anyopaque) FormattingError![:0]const u8 {
        _ = c;
        return std.fmt.allocPrintZ(allocator, "{d}", .{ptrToValue(i32, ptr)});
    }
    pub fn value_f32(c: *Context, allocator: std.mem.Allocator, ptr: *anyopaque) FormattingError![:0]const u8 {
        _ = c;
        return std.fmt.allocPrintZ(allocator, "{d}", .{ptrToValue(f32, ptr)});
    }
    // ...
};

// Format.fns.<name>.func / Format.fns.<name>.name
// for(std.meta.fields(@TypeOf(Format.fns))) |f| { @field(Format.fns, f.name) }

This might seem a bit redundant but it means you can refer to the function pointer plus its name (or other arbitrary data if you change NamedFn) at run time. In your case you might want to directly construct an array of NamedFn values instead of the struct I am creating.

This is basically the namespace version described by @dimdin, but I am constructing a new struct that has field names based on the declaration names.
In my case this makes sense because I have specific places where I want to refer to specific functions, but also want to be able to show debug/introspection info at run time.

I think with the generated struct replaced with an array, that would allow you to declare a set of functions via the struct that gets passed to Table and have it return an array that you can easily iterate over.

If you only need this at comptime, using the namespace directly and its public declarations accessible via @typeInfo is probably easier. But the code in FnTable demonstrates one way to do that.

2 Likes

I think one reason why this might be deliberately difficult is to avoid exposing things to the user which then need to be supported by the compiler and create additional complexity in it, but I am not completely sure, whether there are issues that point out more specific reasons for the current design.

Here is a topic where @chung-leong found a sneaky way to access the function name: Is there a way to get the function name from pointer - #3 by chung-leong

That’s really odd. Great if it works, but that means a single type can have multiple string representations? Hopefully they are the same type. It would be not good if the name affected the type.

deliberately difficult is to avoid exposing things to the user which then need to be supported by the compiler and create additional complexity

That’s true of anything. This seems like a very very useful feature that people are trying all sorts of things to hack around. I’d be highly surprised if this was very difficult. As the hack shows, it clearly is available at the time it would seem.

It is difficult to write some meta tooling without some of this basic stuff. That’s why the cpp macros stuff often exists - not for runtime use in regular programs, but for use in tooling and meta code to allow for things like good assert messages.

Sometime this meta tooling is just really difficult in zig - much harder than it should be.

New problem. You can’t have an array of function bodies. ugh.I’m trying to keep the interface simple. I could wrap the function body in a struct and it is fine (why oh why is that needed), but it makes the call ugly af.

run_cross(..., .{func1, func2},...)

turns into

run_cross(..., .{.{.f=func1}, .{.f=fun2}},...)

It is probably easiest to keep an array of struct { type, []const u8 } at comptime, you should be able to pass function bodies as type type as long as it is a comptime parameter.

Or I guess If you don’t need the name then an array or tuple of type.

they are comptime. this is the actual sitgnature:

pub noinline fn run_count_cross(
    comptime opt: Options,
    name: []const u8,
    invokes: usize,
    comptime FuncType: type,
    comptime funcs: []FuncType,
    comptime fnames: ?[]const []const u8,
    comptime ArgType: type,
    comptime args: []ArgType,
    comptime anames: ?[]const []const u8,
) [funcs.len * args.len]Data {

and i would like to make it ever simpler

pub noinline fn run_count_cross(
    comptime opt: Options,
    name: []const u8,
    invokes: usize,
    comptime funcs: []anytpe, // I know this doens't exist
    comptime args: []anytype, // i just dont to pass the type when already have it
  [funcs.len * args.len]Data {

Unless you pass type to FuncType you are using a more specific type what I mean is to use:

    comptime funcs: []type,

What about this:

pub noinline fn run_count_cross(
    comptime opt: Options,
    name: []const u8,
    invokes: usize,
    comptime funcs: anytype, // tuple of function bodies
    comptime args: anytype, // tuple of arguments
  [funcs.len * args.len]Data {

Also take a look at this topic where we played around with similar ideas: Making Overloaded Function Sets Using Comptime

The basic gist is: keep the original tuple of function bodies and just index into it, deconstruct it or iterate over it wherever you need it. Use @TypeOf(funcs) to get the type and check that it actually is a tuple, otherwise create a compile error.