#How to avoid dependency loop for type builder

1 messages · Page 1 of 1 (latest)

supple kiln
#

Hello, I have two structs that are defined like so:

  • NodeX carries a list of pointers of NodeY
  • NodeY carries a list of pointers of NodeX

A bipartite graph so to speak.

Now you can imagine, there is a lot of potential code duplication, so I wish to use a function that generates type NodeX, NodeY.

However, when I write the following code, it does not compile.

const std = @import("std");
const print = std.debug.print;
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;

const NodeXX = struct {
    list: ArrayList(*NodeYY),

    fn init(allocator: Allocator) error{OutOfMemory}!@This() {
        return @This(){
            .list = try .initCapacity(allocator, 0),
        };
    }

    fn deinit(self: *@This(), allocator: Allocator) void {
        self.list.deinit(allocator);
        self.* = undefined;
    }
};

const NodeYY = struct {
    list: ArrayList(*NodeXX),

    fn init(allocator: Allocator) error{OutOfMemory}!@This() {
        return @This(){
            .list = try .initCapacity(allocator, 0),
        };
    }

    fn deinit(self: *@This(), allocator: Allocator) void {
        self.list.deinit(allocator);
        self.* = undefined;
    }
};


pub fn main() !void {
    var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
    defer {
        const leaked = gpa.deinit() == .leak;
        if (leaked) @panic("leak detected");
    }
    const a = gpa.allocator();
    var c: NodeXX = try .init(a); // replace to NodeX for manual code generation
    defer c.deinit(a);
    
    print("All good\n", .{});
}

How can I make it work?

#

This is the manual automatic approach, which doesn't compile just fine:

const NodeX = Internal(NodeY);
const NodeY = Internal(NodeX);
fn Internal(Node: type) type {
    return struct {
        list: ArrayList(Node),

        fn init(allocator: Allocator) error{OutOfMemory}!@This() {
            return @This(){
                .list = try .initCapacity(allocator, 0),
            };
        }

        fn deinit(self: *@This(), allocator: Allocator) void {
            self.list.deinit(allocator);
            self.* = undefined;
        }
    };
}
jaunty copper
#

(on 0.15.2)

supple kiln
#

oh, i mistakenly swapped NodeX and NodeXX

#

I hadn't enough discord space so I removed one for the comment

#

Full version:

const std = @import("std");
const print = std.debug.print;
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;

const NodeX = Internal(NodeY);
const NodeY = Internal(NodeX);
fn Internal(Node: type) type {
    return struct {
        list: ArrayList(Node),

        fn init(allocator: Allocator) error{OutOfMemory}!@This() {
            return @This(){
                .list = try .initCapacity(allocator, 0),
            };
        }

        fn deinit(self: *@This(), allocator: Allocator) void {
            self.list.deinit(allocator);
            self.* = undefined;
        }
    };
}

const NodeXX = struct {
    list: ArrayList(*NodeYY),

    fn init(allocator: Allocator) error{OutOfMemory}!@This() {
        return @This(){
            .list = try .initCapacity(allocator, 0),
        };
    }

    fn deinit(self: *@This(), allocator: Allocator) void {
        self.list.deinit(allocator);
        self.* = undefined;
    }
};

const NodeYY = struct {
    list: ArrayList(*NodeXX),

    fn init(allocator: Allocator) error{OutOfMemory}!@This() {
        return @This(){
            .list = try .initCapacity(allocator, 0),
        };
    }

    fn deinit(self: *@This(), allocator: Allocator) void {
        self.list.deinit(allocator);
        self.* = undefined;
    }
};


pub fn main() !void {
    var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
    defer {
        const leaked = gpa.deinit() == .leak;
        if (leaked) @panic("leak detected");
    }
    const a = gpa.allocator();
    var c: NodeX = try .init(a); // swap NodeX vs NodeXX for a difference
    defer c.deinit(a);
    
    print("All good\n", .{});
}
jaunty copper
#

well I don't know the best way to fix it but adding a wrapper struct works around the problem because it makes accessing the other type lazy

const NodeX = Internal(struct{ pub const T = NodeY; });
const NodeY = Internal(struct{ pub const T = NodeX; });
fn Internal(NodeHolder: type) type {
    return struct {
        list: ArrayList(NodeHolder.T),

supple kiln
#

yeah, this could work

supple kiln
jaunty copper
supple kiln
#

Omg, i know...

#

I added a single statement:
const Node = NodeHolder.T before the return

#

this also causes the dependency loop detection

jaunty copper
#

ah yeah that won't work, you can add it in the struct itself instead

supple kiln
#

thanks for your help

#

do you think this weird behaviour is on purpose?

#

or is it more of a side product to be "better safe than sorry"

jaunty copper
#

I don't think it will be changed. someone was working on a fix for dependency loop errors and it wasn't going to fix this iirc