#Error in generic struct function

1 messages · Page 1 of 1 (latest)

leaden parrot
#

graph.zig:

const std = @import("std");
const Properties = @import("properties.zig").Properties;
const Vertex = @import("vertex.zig").Vertex;
const Edge = @import("edge.zig").Edge;

// Define the Graph struct with generic properties container
fn Graph(comptime PropsType: type) type {
    return struct {
        vertices: []Vertex(Properties(PropsType)),
        edges: []Edge(Properties(PropsType)),
        metadata: []

        // Constructor for creating a graph using generics
        fn New(comptime PropsType: type) Graph(PropsType) {
            return Graph(PropsType){
                .vertices = []Vertex(Properties(PropsType)),
                .edges = []Edge(Properties(PropsType)),
            };
        }
    };
}

My issue is with fn New(comptime PropType: type), I get an error:

function parameter 'PropsType' shadows function parameter from outer scope

I know I could just change the variable name and use a new variable name instead of PropsType in the New() function, I just want the best zig coding convention advice for implementing generic struct functions.

wicked spoke
#

[]Vertex(Properties(PropsType)) and the other makes no sense

#

You need to allocate those

#

and metadata is empty

#

what is metadata

iron whale
#

You don't need the PropsType parameters for New function.

wicked spoke
#

indeed

#

const std = @import("std");
const Properties = @import("properties.zig").Properties;
const Vertex = @import("vertex.zig").Vertex;
const Edge = @import("edge.zig").Edge;

// Define the Graph struct with generic properties container
fn Graph(comptime PropsType: type) type {
    return struct {
        vertices: []Vertex(Properties(PropsType)),
        edges: []Edge(Properties(PropsType)),
        metadata: []Metadata, // Define wth is this
        allocator: std.mem.Allocator,

        // Constructor for creating a graph using generics
        fn new(ally: std.mem.Allocator, init_size: usize) !Graph(PropsType) {
          return .{
            .vertices = try ally.alloc(Vertex(Properties(PropsType)), init_size),
            .edges = try ally.alloc(Edge(Properties(PropsType)), init_size),
            .metadata = try ally.alloc(Metadata, init_size),
            .allocator = ally,
          };
        }
    };
}
#

This is more zig-like

#

const std = @import("std");
const Properties = @import("properties.zig").Properties;
const Vertex = @import("vertex.zig").Vertex;
const Edge = @import("edge.zig").Edge;

// Define the Graph struct with generic properties container
fn Graph(comptime PropsType: type) type {
    return struct {
        vertices: std.ArrayListUnmanaged(Vertex(Properties(PropsType))) = .{},
        edges: std.ArrayListUnmanagedEdge(Properties(PropsType)) = .{},
        metadata: std.ArrayListUnmanaged(Metadata) = .{}, // Define wth is this
        allocator: std.mem.Allocator,

        // Constructor for creating a graph using generics
        fn new(ally: std.mem.Allocator) Graph(PropsType) {
          return .{
            .allocator = ally,
          };
        }
    };
}

I prefer this

#

since you will be growing stuff

leaden parrot
#

@wicked spoke thx!

leaden parrot
#

I'm leaning towards building ZGraph to be tailored to developers who are familiar with graph theory, and know exactly how they want to build & construct their graphs. In spirit of Zig, I don't want to hide any control flow. I want to provide the most foundational elements for building, traversing, and pathfinding graphs.

There are obviously loads of:

Graph Types (DAG, DiGraph, Tree, etc.) -> Structs
Graph Traversal Algorithms -> Functions
Graph Pathfinding Algorithms -> Functions

main.zig:

const std = @import("std");

const KeyValuePair = @import("./core/properties.zig").KeyValuePair;
const Properties = @import("./core/properties.zig").Properties;
const Vertex = @import("./core/vertex.zig").Vertex;
const Edge = @import("./core/edge.zig").Edge;
const Graph = @import("./core/graph.zig").Graph;

pub fn main() !void {
    const digraph_props = Properties("graph_type", "digraph");
    const example_digraph = Graph.new(digraph_props);

    // 1 ... Add vertices to DiGraph
    // 2 ... Add edges to DiGraph

    // Now you have a barebones Graph Database capable of storing -
    // vertices & edges of any data type

    // Any additional interaction with the DB will be via functions
    // e.g: Graph Traversal and Pathfinder Algorithms
    // e.g CRUD of Vertices/Edges

    // Additional features will be built to make ZGraph multi-threaded,
    // distributed, fault-tolerant, customizable as an ACID-compliant
    // hybrid In-Memory & Disc DB, with disc backups (snapshots).
}
#

properties.zig:

// Define a generic KeyValuePair struct
pub fn KeyValuePair(comptime KeyType: type, comptime ValueType: type) type {
    return struct {
        key: KeyType,
        value: ValueType,
    };
}

pub fn Properties(comptime KeyType: type, comptime ValueType: type) type {
    return struct {
        data: []KeyValuePair(KeyType, ValueType),
    };
}

graph.zig:

const std = @import("std");
const Properties = @import("properties.zig").Properties;
const Vertex = @import("vertex.zig").Vertex;
const Edge = @import("edge.zig").Edge;
const Metadata = @import("metadata.zig").Metadata;

// Define the Graph struct with generic properties container
fn Graph(comptime PropsType: type) type {
    return struct {
        vertices: std.ArrayListUnmanaged(Vertex(Properties(PropsType))) = .{},
        edges: std.ArrayListUnmanaged(Edge(Properties(PropsType))) = .{},
        metadata: std.ArrayListUnmanaged(Metadata) = .{}, // Define wth is this
        allocator: std.mem.Allocator,

        // Constructor for creating a graph using generics
        fn new(ally: std.mem.Allocator) Graph(PropsType) {
            return .{
                .allocator = ally,
            };
        }
    };
}
hexed abyss
#

just fyi I have literally never found a Graph datastructure useful. I almost always want to encode my graphs in my own weird way because it's more efficient

leaden parrot
#

@wicked spoke I added a little more information about my project above. Since you helped me, I spent more time studying Zig and learning about how to write Zig code. Thanks for pointing me in the write direction with some example code.

I have another question if you don't mind. I want the allocator for the graphs to be customizable. Sometimes you want a dynamic heap memory allocator, sometimes it's known at comptime. Can you demo how that fn new() code can be swapped with other allocators?

hexed abyss
#

Algorithms that operate on a generic Context parameter that provides an interface to the actual underlying graph are much more useful

leaden parrot
#

I figured the Graph Traversal Algorithms and Pathfinder Algorithms would be functions, not apart of the Graph structs. Does that fit the criteria of what you're saying?

hexed abyss
hexed abyss
leaden parrot
hexed abyss
#

Oh also, I recommend making your datastructures unmanaged by default. If you really find it necessary, you can add a wrapper that stores unmanaged: GraphUnmanaged, allocator: std.mem.Allocator but I've never hugely needed it except for ArrayList

#

(specifically because of ArrayList(u8).writer)

leaden parrot
#

Do you have any suggestion for my custom allocator conundrum?

leaden parrot
hexed abyss
#

unmanaged means you pass the allocator into each function that needs it

#

Rather than storing it as part of the datastructure

#

It's more explicit and reduces memory usage, particularly if you have many of the datastructure

#

Another style note: init is much more common than new as a function name in zig

#

Though for unmanaged datastructures, you often don't need an init function at all

leaden parrot
#

By datastructure do you mean the actual array/arraylist that stores the data? Managed means the allocator is bundled into the array/arraylist, and unmanaged means that the allocator needs to be called seperately when interacting with data structure?

hexed abyss
#

I mean any datastructure. In this case, Graph

#

Your current definition of Graph is managed, because you're storing the allocator. I'd recommend making it unmanaged, which means you don't store the allocator

leaden parrot
#

Ahh okay got it

#

@hexed abyss in practice is this just removing from the Graph struct:

allocator: std.mem.Allocator,

.allocator = ally,
?

If yes, do you have any code examples i can look at to better understand how & where I'd configure and use memory allocators?

hexed abyss
#

yup, that's it

#

you just add allocator: std.mem.Allocator to the args of any function that actually needs the allocator

#

You can look at std.ArrayListUnmanaged for an example