Stage2: false dependency loop
const S = struct {
a: *[@sizeOf(S)]u8,
};
test {
var s: S = undefined;
_ = s;
}
a.zig:19:10: error: struct 'a.S' depends on itself
a: *[@sizeOf(S)]u8,
^~~~~~~~~~
Should be fixed by implementing lazy pointer types, lazy array types, or both.
This also fails:
pub const DeviceCallback = *const fn (*Device) void;
pub const Device = struct {
callback: DeviceCallback,
};
test {
_ = DeviceCallback;
}
error: dependency loop detected
@Vexu Do you think this is the same bug or should I open a new issue?
It's the same bug.
Slight variation on this issue, this example straight-up crashes the compiler:
const Foo = struct {
ptr: *[1]Foo,
};
test {
const x: Foo = undefined;
_ = x;
}
Weirdly, _ = @as(Foo, undefined) works fine - this example seems specifically related to assignment.
That is a separate bug in the LLVM backend's debug info creation, adding either --fno-LLVM or --strip avoids the crash.
Ah okay, thank you. Is there an open issue for that bug (just so I can reference it in a comment)?
Not that I know of.
Note as a workaround for this I'm just hand-modifying the cimport.zig to just replace the pointers with anyopaque pointers. I've only had this issue with C imports (probably due to the style of C this sort of reference is common -- I know you can get this in pure Zig too I just haven't seen or written code in practice that does). This works for now!
The following also fails:
const std = @import("std");
const node_size = 16;
const Tag = enum(u8) {
none,
some
};
pub const Maybe = extern union {
none: extern struct {
tag: Tag = .none,
padding: [node_size - @sizeOf(Tag)]u8 = undefined,
},
some: Some,
};
const Body = extern struct {
maybe: Maybe = Maybe{.none = .{}},
};
const Some = extern struct {
const padding_len = node_size - (@sizeOf(Tag) + @sizeOf(usize));
tag: Tag = .some,
padding: [padding_len]u8 = [_]u8{0} ** padding_len,
body: *Body,
};
pub fn main() !void {
std.debug.print("{any}\n", .{@sizeOf(Maybe)});
}
Interestingly when body: *Body, is replaced with body: *Maybe, it compiles fine.
That looks very similar to my original case that I was unable to reduce.
i'm having a similar issue. is this the same thing? if not is there already an issue for this or should i make a one?
really wanting this feature for generated code in my protobuf-zig lib so that i don't have to resort to generating duplicate .c,/h files to achieve this.
// /tmp/tmp.zig
pub const A = extern struct { // same happens w/ non-extern struct
b: *const B,
};
pub const B = extern struct {
a: *const A,
};
const a = A{ .b = &b };
const b = B{ .a = &a };
test {
_ = a;
}
$ zig test /tmp/tmp.zig
/tmp/tmp.zig:11:1: error: dependency loop detected
const a = A{ .b = &b };
^~~~~~~~~~~~~~~~~~~~~~
This issue is about types incorrectly depending on themselves while yours is caused by declarations depending on each others address recursively and reduces to:
const a = &b;
const b = &a;
test {
_ = a;
}
This issue is about types incorrectly depending on themselves while yours is caused by declarations depending on each others address recursively and reduces to:
Thanks. Created #14517
This also fails:
pub const DeviceCallback = *const fn (*Device) void; pub const Device = struct { callback: DeviceCallback, }; test { _ = DeviceCallback; }error: dependency loop detected
I hit this as well, in basically the same situation. Eventually I resorted to using *anyopaque instead of my equivalent of *Device and casting it in each callback. Is there a better workaround than this?
pub const DeviceCallback = *const fn (*anyopaque) void;
fn someCallback(p: *anyopaque) void {
const device: *Device = @ptrCast(@alignCast(p));
}
Running into this in Bun, but there’s no stack/return trace other than the comptime function generating the type, which does not point to the line causing a dependency loop
note: the MultiArrayList error is unrelated & usually means UB in zig’s compiler. It happens when any compiler error occurs in a a non-root module within the build
Just in case, one more example.
This one does not compile:
const std = @import("std");
const Foo = struct {
const FnPtr = *const fn (*Foo, u8) void;
data: u8,
cb: FooCallBack,
const FooCallBack = struct {
fptr: FnPtr,
data: u8,
};
fn init(fptr: FnPtr) Foo {
return .{
.data = 7,
.cb = .{.fptr = fptr, .data = 8},
};
}
fn call(foo: *Foo, data: u8) void {
foo.cb.fptr(foo, data);
}
};
fn bar(foo: *Foo, data: u8) void {
std.debug.print (
"data-1 = {}, data-2 = {}, data-3 = {}\n",
.{foo.data, foo.cb.data, data}
);
}
pub fn main() !void {
var foo = Foo.init(&bar);
foo.call(9);
}
Get
222-b.zig:6:5: error: dependency loop detected
const FnPtr = *const fn (*Foo, u8) void;
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
But this one (without "construtor") compiles:
const std = @import("std");
const Foo = struct {
const FnPtr = *const fn (*Foo, u8) void;
data: u8,
cb: FooCallBack,
const FooCallBack = struct {
fptr: FnPtr,
data: u8,
};
pub fn call(foo: *Foo, data: u8) void {
foo.cb.fptr(foo, data);
}
};
fn bar(foo: *Foo, data: u8) void {
std.debug.print (
"data-1 = {}, data-2 = {}, data-3 = {}\n",
.{foo.data, foo.cb.data, data}
);
}
pub fn main() !void {
var foo: Foo = .{
.data = 7,
.cb = .{.fptr = &bar, .data = 8},
};
foo.call(9);
}
I'm facing a very similar issue with dependency loops with regard to function pointers, although I'm not sure it's the exact same (tested in 0.13 and 0.12.1):
const std = @import("std");
const A = struct {
field: std.meta.Tuple(&.{ i32, B }),
};
const B = union(enum) {
thing: *const fn (a: *A) void,
};
pub fn main() void {
const b: B = undefined;
_ = b;
}
Weirdly enough, the dependency loop error does not occur when using an ordinary struct instead std.meta.Tuple in A, or when B is a struct instead of a tagged union.
the recent changes to tuples (suspected cause) now make the following code error (tested on 0.14.0-dev.2335+8594f179f):
test {
_ = Foo;
}
const Foo = struct {
// ^~~~~~
// error: struct 'main.Foo' depends on itself
field: *struct { Foo },
};
the code compiles properly on 0.13.0
I've encountered a similar issue with std.ArrayListAligned. The code:
const std = @import("std");
const Node = struct {
child: ?*NodeList,
};
const NodeList = std.ArrayListAligned(Node, @alignOf(Node));
pub fn main() void {
var test_node = Node{
.child = null,
};
test_node = test_node;
}
fails to compile with
example.zig:3:14: error: struct 'example.Node' depends on itself
It works fine when I replace child: ?*NodeList on line 4 with child: ?*Node.
The error occurs with every version of Zig I've tested, so I'm not sure if I'm just doing it wrong.
Another example. I've been struggling with dependency loops forever, and here is the most recent:
const execute = struct {
const ThreadedFn = packed struct {
f: Fn,
const Fn = *const fn (
process: *Process,
) void;
};
};
const Process = struct {
m: [@sizeOf(P)]u8,
const P = extern struct {
h: Fields,
const Fields = extern struct {
debugFn: execute.ThreadedFn,
};
};
};
fn f(_: *Process) void {}
test "die" {
const tfn = execute.ThreadedFn{.f = &f};
var p: Process = undefined;
tfn.f(&p);
}
The m field and the P struct are part of my effort to break the dependency loop. I have a workaround, as the size of the Process actually is a constant, and I tune the size of the P struct to match it. The h:Fields is just to mimic my code... the loop remains with debugFn directly in P. This is probably just a more complex example of the top post problem.
Categorizing under #24637 for the moment, but I'm pretty certain we're going to want to just accept this restriction in the language. Solving this would introduce a lot of language complexity.
This problem specifically could be solved by assuming pointers to always be sized @sizeOf(usize) bytes. This currently holds true even for @sizeOf(*void). @sizeOf(SomeStruct) doesn't have to fully resolve the type. Am I missing something?
I've recently abandoned having the named type
const Fn = *const fn (
process: *Process,
) void;
and just put in the whole *const fn... stuff everywhere. Ugly, but it works.
This problem specifically could be solved by assuming pointers to always be sized
@sizeOf(usize)bytes. This currently holds true even for@sizeOf(*void).@sizeOf(SomeStruct)doesn't have to fully resolve the type. Am I missing something?
It is true that this is logically possible. Unfortunately, type resolution is an area where Zig's comptime forces us to make some design compromises. Typical languages like C kind of have this easy, because types are their own thing with a limited grammar; they can simply see that the type is a pointer without "looking at" the whole type. That doesn't work in Zig: field types are expressions, and the only way to figure out the value of that expression is to evaluate it. After all, instead of this:
const Foo = struct {
ptr: *[@sizeOf(Foo)]u8,
};
I could have written this:
const Foo = struct {
ptr: (T: {
const n = @sizeOf(Foo);
const Array = [n]u8;
if (true) break :T *Array;
@compileError("you can't get here!");
}),
};
...which clearly means the exact same thing by Zig's semantics, but I hope it's clear that handling that makes the job much, much harder. Bear in mind that this labeled block could do anything between const Array = ... and the break, so if you were trying to evaluate the type "lazily", you'd need to make every single operation which could touch Array check whether it needs to resolve that type. We have systems like this in the compiler to try and make type resolution more lazy -- in practice they cause lots of annoying bugs.
The way type resolution works in Zig today is held together with duct tape and string: it's extremely tied to the implementation, essentially impossible to codify into a language specification, and is chock-full of bugs (see sub-issues of #24637), some of which are very serious and essentially impossible to solve. I'm currently working on a potential change which sacrifices a couple of small capabilities, but makes the behavior consistent, and allows a few things which clearly should be allowed, alongside fixing some issues with incremental compilation. If that branch ends up making it into master, it will not allow this.
By the way, if you're hitting this issue, you can just use a [*]u8 field instead! You can even slice it (bytes[0..@sizeOf(T)]) when you use it to get the same behavior. Obviously it would be preferable for it to work directly, but it's pretty easy to work around this limitation.
@mlugg In this potential change to the type system, will the following snippet work ?
pub const DeviceCallback = *const fn (*Device) void;
pub const Device = struct {
callback: DeviceCallback,
};
test {
_ = DeviceCallback;
}
I think it's an extremely common pattern (much more than the one described in the issue itself) and currently it doesn't work, even though every programming language with function pointers allow this kind of use.
@zenith391 yep, that snippet works fine on my in-progress branch -- it's one of the cases in #24637 which I specifically wanted to make sure I solved.