zig build-exe segfaults when compiling the following example. zig build-lib and zig build-obj also shows same fault. Build succeeds when -fllvm is passed. Also only Debug builds have this issue - no fault when -O ReleaseSafe is used.
I think this is a regression in 0.15.1 compiler as the same works fine in 0.14.1. I am not sure if there is already a issue for the same, but I could not find one.
Source
const std = @import("std");
const ISpeaker = struct {
speak: fn () void,
};
const Human = struct {
speaker: ISpeaker,
};
fn say_hello() void {
std.debug.print("Hello\n", .{});
}
pub fn speak(speaker: *const ISpeaker) void {
speaker.speak();
}
pub fn main() !void {
const h = Human{
.speaker = .{
.speak = say_hello,
},
};
// This fails
speak(&h.speaker);
// This works
h.speaker.speak();
// This works
const i = ISpeaker{ .speak = say_hello };
speak(&i);
}
The segfault is definitely a regression though I donāt think it should compile regardless, function body types are compile time only, the runtime counterpart function pointers which you probably meant to use are *const fn(args) ret.
more like a compile time handle to a function of that signature, kinda like a pointer ig, one where the compiler nows 100% what function Itās referring so will likely be inlined and optimised.
the regression is the segfault, but yes it should have compile errored in 0.14, that could also be a regression, but I canāt be bothered to chase that right now.
To clarify, the part that should not compile is passing a/asking for a runtime pointer to a type that has a field with compile time only type.
You could force the argument to be comptime or maybe even call the function with comptime, not sure about the latter, and the compiler would try to chase the pointer down at comptime, and it could work, or throw an error if it canāt. That might be the part that segfaulted, though it shouldnāt have gotten there without a comptime somewhere.
What does function body types mean? Documentation also does not say much
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 you can basically think of function body types as the type that defines a specific function signature and the function body is like a handle that refers to a specific function that has this function signature.
You can think of it as a reference to a function that is only valid at comptime, because it refers to a function that is stored somewhere in the running compiler, once the compiler generates runtime code, that code canāt contain any function bodies, because otherwise the code that you are compiling wouldnāt be independent from the compiler that is compiling it.
Instead the functions which are actually used get compiled into the program and then you can use function pointers to refer to them.
Also you can do things with function body types that you canāt do with ānormalā functions or function pointers, for example they allow you to do comptime higher order programming, having function bodies and function pointers as separate types makes a clearer distinction between what you can/canāt do with those types and when.
Function bodies are something you can use in comptime meta programming, while function pointers are more general and also work at runtime.
Iām not sure that this should actually be treated as runtime-known. Itās a const in the main function, so if I understand things correctly, it can propagate on a comptime-known basis. Example: indexing out-of-bounds with a const-declared usize is a compile error, to trigger it at runtime one has to use a var.
Iām not sure one way or the other if this premise applies to function bodies, however. I think we can agree that function pointers are what is intended by the code in question, itās whether a pointer-to-constant-function-body can be deferenced to get a function body back under these circumstances which Iām fuzzy on.
Getting a pointer to that type is ofc fine, itās the passing it to a function which isnāt being called in comptime and the function accepting a runtime pointer to that type which can only exist at comptime, I would expect an error requiring the parameter to be marked comptime or the function to be called in comptime.
There is also the option of the parameter being implicitly comptime, but I donāt like that. With type or fn body parameters that make sense, but for a non language primitive type I would prefer to have to explicitly mark it as comptime either the call or the parameter. I think the latter makes more sense.
Right, this is an important aspect of the coming months and years: things like this need to migrate from āthis is how things workā to āthis is how things must workā, and with enough āwhyā to make it hold together. Itās no too surprising that thereās a compiler regression here, because ācall function body by passing constant pointer to a struct type which can only exist at comptimeā is a weird thing to do.
Iām inclined to think that a function body parameter should just be implicitly comptime, like an enum literal or a type. They canāt be passed around at any other time, so labeling them is redundant: if you try to pass a function pointer as a function body, that wonāt compile.
Thereās a subtle distinction here between āin comptimeā and comptime known, though. Itās totally legit to call a function with comptime parameters at runtime, as long as all the comptime parameter are comptime known. Totally ordinary in fact, most of std.mem receives a type as the first parameter, and the rest are allowed to be runtime-known (whether they happen to be or not).
This example isnāt that simple, of course, because weāre talking about a comptime-known field type of a struct. But itās still the case that weāre dealing with a type which can only exist in comptime, and itās tricky (for me at least) to come up with a nice tight rule which would require that, but not e.g. a type, to be annotated with comptime.
Iāve never personally made a struct with a field of type type, but I have to presume the same thing happens: the composite gains ācomptime taintā, and can only be instantiated by assigning that field using comptime-known information. Kinda has to be, since runtime-known types do not exist.
In principle, I see no difference between a function where some parameters must be comptime-known, and a struct where some fields must be comptime-known. The latter could even be useful in calling the former!
Not to nitpick, but to show that itās a totally normal thing - while you may not have created your own struct type with a field of type type, youāve surely instantiated many such, such as std.builtin.Type.StructField, etc.