#dependency loop with arrayList.init

1 messages · Page 1 of 1 (latest)

rigid lantern
#

Hi All, I was trying to compose some structs with unions and ArrayList and hitting a dependency loop:

const std = @import("std");

var raw = std.heap.GeneralPurposeAllocator(.{}){};
pub const ALLOCATOR:std.mem.Allocator = raw.allocator();

pub const Value = struct {
    value: i32 = 8,
};

pub const Sequence = struct {
    children: std.ArrayList(Container) = std.ArrayList(Container).init(ALLOCATOR),
};

pub const Stack = struct {
    children: std.ArrayList(Container) = std.ArrayList(Container).init(ALLOCATOR),
};

pub const Container = union(enum) {
    val: Value,
    sta: Stack,
    seq: Sequence,
};

test "arraylist_union" {
    var st = Stack{};
    var sq = Sequence{};
    const v = Value{ .value = 12 };

    try sq.children.append(.{ .val = v });
    try st.children.append(.{ .seq = sq });

    try std.testing.expectEqual(
        v.value,
        st.children.items[0].seq.children.items[0].val.value
    );
}

gives me the error:

.../zig/zig-macos-aarch64-0.10.1/lib/std/array_list.zig:51:13: error: dependency loop detected
        pub fn init(allocator: Allocator) Self {
        ~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
referenced by:
    Sequence: .../union_with_arraylist.zig:10:22
    Container: .../union_with_arraylist.zig:21:10
    Container: .../union_with_arraylist.zig:18:23
    Sequence: .../union_with_arraylist.zig:11:56

Is the problem here the initialization in the struct? Or the composition?

#

If I remove the intializers and do those at the construction sites, then it works:


pub const Sequence = struct {
    children: std.ArrayList(Container),
};

pub const Stack = struct {
    children: std.ArrayList(Container),
};

pub const Container = union(enum) {
    val: Value,
    sta: Stack,
    seq: Sequence,
};

test "arraylist_union" {
    var st = Stack{.children = std.ArrayList(Container).init(ALLOCATOR)};
    var sq = Sequence{.children = std.ArrayList(Container).init(ALLOCATOR)};
    const v = Value{ .value = 12 };

    try sq.children.append(.{ .val = v });
    try st.children.append(.{ .seq = sq });

    try std.testing.expectEqual(
        v.value,
        st.children.items[0].seq.children.items[0].val.value
    );
}
#

so is the problem trying to do them all at the comptime scope at the same time?

smoky grotto
#

I think the problem might be just that in order for it to resolve the initializer, it must know what Container is - but that isn't yet resolved, because it's in the middle of checking the seq: Sequence field - so Container doesn't yet have a type that can be used to figure out what std.ArrayList(Container) is yet.

#

Possibly could be an oversight. 🤔

rigid lantern
#

oversight at the compiler level you mean?

smoky grotto
#

Right

rigid lantern
#

I can file an issue in that case

#

thanks!

smoky grotto
#

o7 😄

rigid lantern
pulsar ermine
# rigid lantern Hi All, I was trying to compose some structs with unions and ArrayList and hitti...

Unrelated to whether or not this is a bug, but the working version of the code is definitely the more "zig" way of doing things. The global var allocator itself is something generally worth avoiding. In your test cases, it's usually best to use std.testing.allocator which uses an already setup GeneralPurposeAllocator to give you automatic leak checking, double free detection, etc.

test "arraylist_union" {
    var st = Stack{.children = std.ArrayList(Container).init(std.testing.allocator)};
    var sq = Sequence{.children = std.ArrayList(Container).init(std.testing.allocator)};
    // ...
}

(using std.testing.allocator here would also show that you are leaking memory in this test since you're not deiniting your ArrayLists)

If you still want to be able to have a default value for children, you could use std.ArrayListUnmanaged instead which doesn't store its allocator:

pub const Sequence = struct {
    children: std.ArrayListUnmanaged(Container) = .{},
};

pub const Stack = struct {
    children: std.ArrayListUnmanaged(Container) = .{},
};

this then defers the need for specifying an allocator to the callsite of any append, etc calls (and to deinit).