The main problem with your code is that you are not allocating the memory properly.
When you say:
pub fn addNode(head: *Node, value: u8) !void {
...
const newNode = Node{
.value = value,
.next = null,
};
}
you are creating newNode on the stack of the function addNode, so the pointer to this memory becomes invalid as soon as the function returns.
An easy way to tackle this is to pass an allocator down ton addNode, which allocates separate memory, which will remain live after addNode().
pub fn main() !void {
// a simple allocation strategy that uses main()'s stack
// and a fixed buffer o 1024 bytes.
var buf: [1024]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buf);
const allocator = fba.allocator();
addNode(allocator, &head, 1);
// ...
}
pub fn addNode(allocator: std.mem.Allocator, head: *Node, value: u8) !void {
// ...
const newNodePtr = try allocator.create(Node);
newNodePtr.* = Node{
.value = value,
.next = null,
};
currentNode.next = newNodePtr;
}
Note that the pointer newNodePtr is still const, but that does not say anything about the value behind that pointer; it merely means that we don't change the value of the pointer.
Notice also that buf is what holds the individual bytes, and this part is variable. fba is what holds a reference to buf, plus other fields that keep track of which part of buf is occupied, etc. so that needs to be variable as well.
allocator, on the other hand only holds reference to fba, and that is not going to change, so allocator is const. (I could have also passed fba.allocator() directly, which also makes it const since function arguments are always const in Zig.)
Finally, allocator.create() is "talking" to the fba in the background.
Here's altered version of your code:
const std = @import("std");
const Node = struct {
value: u8,
next: ?*Node,
};
fn printList(head: *Node) void {
var currentNode = head;
while (currentNode.next) |next| {
std.log.warn("node.value={d}", .{currentNode.value});
currentNode = next;
}
std.log.warn("node.value={d}", .{currentNode.value});
}
pub fn main() !void {
var buf: [1024]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buf);
const allocator = fba.allocator();
var head = Node{
.value = 0,
.next = null,
};
try addNode(allocator, &head, 1);
try addNode(allocator, &head, 2);
try addNode(allocator, &head, 3);
printList(&head);
}
fn addNode(allocator: std.mem.Allocator, head: *Node, value: u8) !void {
var currentNode = head;
while (currentNode.next) |next| {
currentNode = next;
}
const newNodePtr = try allocator.create(Node);
newNodePtr.* = Node{
.value = value,
.next = null,
};
currentNode.next = newNodePtr;
}
I've made several extra changes:
addNode does not need to be pub since it's only used within this main module.
You don't need to say currentNode.*.next; Zig will automatically dereference pointers in this context.
I've simplified the while loop and used capture. This gets rid of the optionality since the loop will only run if currentNode.next is not null, and next will be of type *Node, not ?*Node.
Notice the newNodePtr.* = Node{..} syntax; this is equivalent to creating new Node and copying it to the pointer address.
I've renamed newNode to newNodePtr. It's not very common to add this suffix, but I found it helpful when learning Zig.
constbecauseconst newNodeis …constvarcompiles for the assignment shown in the errorcurrentNode.*.next = &newNode;