#syntax
1 messages · Page 1 of 1 (latest)
correct
Correct
Zig's syntax for types is built in such a way that you can just read the information left-to-right
?*u8 - ? optional, * pointer (to), u8 unsigned 8 bit
*?u8 - * pointer (to), ? optional, u8 unsigned 8 bit
thanks
after i get the basic lang figured out i'm probably gonna use zig to figure out implementing basic data structures
probably starting with a linked list or similar
kinda new/inexperienced
do you know another unsafe low-level language (C / C++)?
many people new to Zig have a hard time understanding pointer lifetimes, if you're not experienced with those you might want to read on that
ive touched cpp because college and i dont like
when you say pointer lifetimes i think <'a>
You are not wrong
just that now is all manual (Thankfully there are like... 2 states lol)
well, that's the way Rust codifies them. the concepts exist in all low-level languages, but Rust is the only (big) languages that has them as a part of its type system
Inside the same function
Outside the same function
(During all the program duration is just outside the same function)
ig cpp:unique_ptr/shared_ptr has something to do with pointer lifetimes probably?
Kind-of
i think i probably have a basic idea
Those are automatic memory freeing on sharing semantics
well, just make sure you're not leaking pointers out of their scope
fn oops() *u8 {
var value: u8 = 0;
return &value; // oh no! we get a pointer to a value that is dead when the function exits
}
oh yeah i ran into that one in cpp already
dangling pointer i think the compiler calls it
yes.
memory management in Zig is all manual, you as the programmer need to make sure you never get into one of these situations
After some good practices are on you, is rare you hit them
yeah, i picked zig because of the low level appeal of that kind of thing
i like working with simpler lower abstraction stuff
my first programming was on a ti-83 :>
the whole no classes thing is different for me though, its been a minute since ive used something without classes. i basically just used them as method structs though so 🤷
Zig has structs and unions for bundling and abstracting over data; the only thing you lose is inheritance (good!)
if you do find yourself needing some dynamic dispatch you can look at how std.mem.Allocator is implemented. for static dispatch (aka generics, parametric polymorphism) look at std.ArrayList, generics in Zig are (uniquely) just ordinary functions that run at compile-time
yeah i was already trying to use unions and structs
what i'm currently at:
pub const linkedList = struct {
const data = union(dataType) { int: i8, char: u8 };
const next: *?linkedList = null;
pub fn at(i: usize) OutOfBounds!linkedList {
var resultList: *?linkedList = @This();
for (0..i) |idx| {
_ = idx; // autofix
if (resultList.*.next.* == null) {
return OutOfBounds;
}
resultList = resultList.*.next;
}
}
pub fn push(value: dataType) {
var curElement = @This();
while(curElement.next.* != null) {
curElement = curElement.next.*;
}
}
there are a few problems in your code, do you want me to point them out?
im aware there's no return statement in at, i just added it after looking at it
push isnt even close to done yet too
there are a few others...
what
sidenote, is there a ``` lang tag that highlights okay for zig
alright.
first, your use of @This: @This is a builtin that returns you the type definition you're currently in, if you're inside of linkedList the output of @This would be that type. it's not like the this pointer in C++ or similar.
if you want to get an item, add a self argument to your methods:
pub fn at(self: *linkedList, i: usize)
pub fn push(self: *linkedList, value: dataType)
we ususally use rs
damn you, google!
okay
methods in Zig allow for invocation using the dot syntax: linkedList.at(ll, 10) can be re-written as ll.at(10)
so like methods in any other language then
in any other... good language
yes
some languages require you to use dot syntax, you can't write out the first version
oh
is there a reason one would need the first version if the second version exists
higher order functions
sorry
i know that term, but not what it actually means
something something functional programming
that's a bit of a rabbithole, but yes, something something functional programming, it's a good reason though!
Indeed
Curry(me(this),yes)
ah
i thought currying was like
some_function(thing)(other_thing)
there is a little problem with the return type of at. you probably want to change it to error{OutOfBounds}!linkedList, so "an error set with the singular code OutOfBounds, or a value of type linkedList"
when returning the error, write return error.OutOfBounds instead of return OutOfBounds
hmmm... why are you holding the next as a *?linkedList and not ?*linkedList? the current approach requires you to store in memory a ?linkedList for every node, instead of just a linkedList
because i had that thought, asked this question on syntax originally and didnt change it after
ah ok lol
that's a bit of a nitpick, but you should perhaps follow Zig's naming conventions (PascalCase for types, snake_case for variables, etc)
It is
I see that in at's for loop you're discarding the iteration variable. if you don't need it you can simply write out for (0..i) |_|
that's what I see for now - ya smart, you seem to know how to do these things.
just remember that Zig's semantic analysis engine is super lazy: if you don't use a piece of code it will not type-check it! make sure to write out a test block for every function you create
manual allocations and deallocations are something you gotta be aware of in Zig, no RAII is going to save you!
read into how Zig does that, defers and friends, I'm sure you can manage
i read the keyword defer in zig.guide
i get that it does something on scope exit but i don't get how that relates to allocations
the idiomatic way to create a value that you need to free when going out-of-scope is to init the value, and stick a defer foo.deinit() right after:
var my_array_list = std.ArrayList(u8).init(allocator);
defer my_array_list.deinit();
ah
Or if the data is going out but you still wanna free on error is with errdefer
on the topic of errors
is there a way to pass a value as const
src/structures.zig:35:32: error: expected type '[]const u8', found 'u8'
expect(std.mem.eql(u8, list.data, 'a'));
~~~~^~~~~
array of const u8 expected
you're passing in a singlular u8
yes
not a slice of them
std.mem.eql is for comparing slices, it seems like you want to compare individual u8s, use list.data == 'a' for that
(I also assume list.data has changed since you posted your code, else I'd expect it to error with a different message)
it has
i decided i can worry about unions later
this is throwing something about needing to be known at comptime?
pub fn push(self: *linkedList, value: u8) void {
var curElement: ?*linkedList = self;
while (curElement.?.*.next != null) {
curElement = curElement.?.*.next;
}
curElement.?.*.next = ?*linkedList.init(value);
}
src/structures.zig:28:49: error: unable to resolve comptime value
curElement.?.*.next = ?*linkedList.init(value);
what's the push function supposed to do?
add an element to the linked list
to... the end..? of it?
yes
ok...
in the last line, on the right hand side of the =, you wrote ?*..., you're essentially declaring a type there, since ? and *, when used as prefix operators, make types
oh
how do i make that line happy then
i can't un ? * on lhs, that throws a different error
Zig tries to create this type (it still does not know that that will be a type mismatch later), and because types can only exist at compile time it tries to call linkedList.init(value) at compile time, and fails because value is runtime known.
can you share the body of init? I worry it itself might have some problems...
pub fn init(value: u8) linkedList {
return linkedList{ .data = value };
}
oh yeah that's gonna cause some problems...
can you explain exactly what you're trying to do in the last line of push? I suspect I know what you got wrong...
i'm trying to assign the pointer next to a new linkedList
every permutation i've tried involving dereferencing, adding a reference on rhs, etc has either errored or panicked
ok
so let's go over this together...
let's begin with finding the last element of the linked list; what you did is alright, but we can do somewhat better...
your variable, curElement, is of type ?*linkedList - why is that? we know the pointer points to an existing node, so *linkedList would work just fine (I also took the liberty to use snake_case):
var cur_element: *linkedList = self;
and because of type inference we can leave out the type annotation:
var cur_element = self;
now for the iteration...
you check if the next value isn't null like so: cur_element.?.*.next != null
because cur_element is now not optional we need not the .?, arriving at cur_element.*.next != null
but we can do better...
first, Zig has some syntax sugar for getting a field out of a pointer-to-struct. if I have a foo variable of type *Foo and I want to access it's bar field, I can write it like foo.*.bar, and also as foo.bar - Zig inserts the dereference
so we write cur_element.next != null
pub fn push(self: *linkedList, value: u8) void {
var curElement = self;
while (curElement.next != null) {
curElement = curElement.next.?;
}
curElement.next.? = linkedList.init(value);
}
we can do even better...
we can eliminate the boolean check for null:
if you have a nullable value, and you want to execute one branch if it is non-null, and another if it is, we can write:
if (nullable_value) |non_null| {
// something
} else {
// something else
}
if the type of nullable_value is ?i64, the type of non_null will be i64, and will contain the unwrapped value in case the argument is non-null
this is if with optionals
this also works with while loops, so we can write it like so:
var cur_element = self;
while (cur_element.next) |next| {
cur_element = next;
}
// ...
no need for explicit unwrapping!
and for the last part... appending a value...
thought if/while only evaluate booleans and error unions
notice the capture (|non_null|) at the end
cur_element.next needs to be set with a value of type ?*linkedList (or just a *linkedList, that will type-coerce into an optional)
where do we get that pointer-to-linked-list from? init returns us a linkedList, not a pointer thereof...
to reference a value, and get a pointer pointing at it, use the addr-of operator: &
I'll let you try and finish the code with that knowledge, do send it here, because you most likely will have problems
i tried that and got a const complaint
say... do you know Rust? I can explain how Zig's ? relates to Rust's Option
not Rust's ? operator (that's actually Zig's try!)
I mean the optional type, and pattern matching
yeah,
match thing {
Some(1) => {return Err(something::terrible::happened},
_ => 0,
None() => 0
}
when we want to pattern-match on an optional in Rust we write:
// Rust
match x {
Some(deffo_x) => todo!(),
None => todo!(),
}
or with if let:
// Rust
if let Some(deffo_x) = x {
todo!()
} else {
todo!()
}
the 2nd version is nearly exactly Zig's unwrapping:
// Zig
if (x) |deffo_x| {
// TODO
} else {
// TODO
}
rust compiler will warning you on those paren on the top
oh yeah lol
doing too much Zig recently...
in any case...
we got to this situation:
pub fn push(self: *linkedList, value: u8) void {
var cur_element = self;
while (cur_element.next) |next| {
cur_element = next;
}
cur_element.next = // ???
}
what do you think we need to do now? even if your code errors, what did you come up with?
& but that complains that const was not expected
can you send the code?
pub fn push(self: *linkedList, value: u8) void {
var curElement: *linkedList = self;
while (curElement.*.next != null) {
curElement = curElement.*.next.?;
}
curElement.*.next.? = &linkedList.init(value);
}
no, i tried having the init return the ref too but it had the same complaint about const
if it was rust:
pub fn push(self: *linkedList, value: u8) void {
var curElement: *linkedList = self;
while (curElement.*.next != null) {
curElement = curElement.*.next.?;
}
curElement.*.next.? = &mut linkedList.init(value);
}
ok so, what we have here is that a temporary value that we get from linkedList.init(value). that value, due to its temp-ness, is constant, and so using & on it will yield a *const
what we need to do it set it as it's own variable, and reference that, this will give us a mutable pointer:
var node = linkedList.init(value);
cur_element.next = &node;
but as you figured out already this causes some lifetime issues (hooray! 🎉)
danglingPointerException
not even that!
best case scenario we get a segfault
moving on...
we'll have to allocate some space for the new node we're creating - this requires the use of allocators
if we were in C land, we could have used malloc. but in Zig we don't have any omniscient global allocator laying around - we gotta get one somehow
reading on allocators rn
std.heap.page_allocator?
using the page allocator is probably a bad choice...
I think it's best if you take some time to read on allocators, and after you'll know how to solve the problem
segfault
can you share the code?
was copying it
next: ?*linkedList = null,
pub fn init(value: u8) !*linkedList {
var arena = heap.ArenaAllocator.init(heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
var result: *linkedList = try allocator.create(linkedList);
result.data = value;
return result;
}
pub fn at(self: *linkedList, i: usize) error{OutOfBounds}!linkedList {
//
}
pub fn push(self: *linkedList, value: u8) !void {
var curElement: *linkedList = self;
while (curElement.next) |next| {
curElement = next;
}
curElement.next.? = try linkedList.init(value);
}
removed defer, probably not wanted in this case
still segfaults
Zig really wants you to be explicit about where your code allocates dynamic memory
sometimes it's quite cucumbersome, but Zig is built for optimal code, and so we live with it...
there are a few allocators that exist in the standard library: std.heap.GeneralPurposeAllocator, std.heap.page_allocator, etc
those actually hold inside the data required to manage the memory, for a GPA that will be a bunch of info on the different buckets and many other smart things.
we, as people who just want to use an allocation strategy, shouldn't use any of those directly. instead, we should request an allocator argument to our functions, of type std.mem.Allocator
a std.mem.Allocator is a pair of pointers that we got from some allocator implementation, and allows us to call the allocation/deallocation functions of said implementation
in your case, for example, the function push needs to allocate memory, and so we'll add an allocator argument:
pub fn push(self: *LinkedList, value: u8, allocator: std.mem.Allocator) error{OutOfMemory}!void
also notice the possible error - for the case when the allocation failed
okay, so init should take an allocator as an argument then too
for now let's make all allocations externals. the linked list will not hold any std.mem.Allocator values inside
both push and init need an allocator argument, yes
inside of test blocks you can use std.testing.allocator; it'll check for any leaked memory / double frees / other bugs
my allocator is complaining about missing argument. the argument wanted is self
send the error message please?
src/structures.zig:40:45: error: expected 1 argument(s), found 0
var list = try linkedList.init('a', gpal.allocator());
~~~~^~~~~~~~~~
/snap/zig/11356/lib/std/heap/general_purpose_allocator.zig:302:13: note: function declared here
pub fn allocator(self: *Self) Allocator {
~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(00:07:59)(kodi)(~/Docum/proje/zig-datastructures)(1)zig test ./src/structures.zig
src/structures.zig:40:45: error: expected 1 argument(s), found 0
var list = try linkedList.init('a', gpal.allocator());
~~~~^~~~~~~~~~
/snap/zig/11356/lib/std/heap/general_purpose_allocator.zig:302:13: note: function declared here
pub fn allocator(self: *Self) Allocator {
~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(00:08:40)(kodi)(~/Docum/proje/zig-datastructures)(1)zig test ./src/structures.zig
src/structures.zig:39:27: error: expected 1 argument(s), found 0
const allocator = gpal.allocator();
~~~~^~~~~~~~~~
/snap/zig/11356/lib/std/heap/general_purpose_allocator.zig:302:13: note: function declared here
pub fn allocator(self: *Self) Allocator {
ummm
what's gpal and where is it defined?
const gpalc = heap.GeneralPurposeAllocatorConfig{ .safety = true };
const gpal = heap.GeneralPurposeAllocator(gpalc);
const allocator = gpal.allocator();
var list = try linkedList.init('a', allocator());
try list.push('b', allocator());
try expect(list.data == 'a');
if (list.at(1)) |ll| {
try expect(ll.data == 'b');
} else |e| {
std.debug.print("{any}", .{e});
}
make gpal var, and remove the () after uses of allocator
src/structures.zig:38:9: error: variable of type 'type' must be const or comptime
var gpal = heap.GeneralPurposeAllocator(gpalc);
^~~~
ah, pff
yes. add {} after heap.GeneralPurposeAllocator(gpalc)
compiles but segfault
what's the trace
in push, while(curElement.next)
heap.GeneralPurposeAllocator(gpalc) is a type (heap.GeneralPurposeAllocator is generic over the config)
to create an instance of an aggregate type, we use {} after, and set the fields inside (here we don't want to set any fields)
Test [1/1] structures.test.ll... General protection exception (no address available)
/home/kodi/Documents/projects/zig-datastructures/src/structures.zig:29:26: 0x1039526 in push (test)
while (curElement.next) |next| {
^
/home/kodi/Documents/projects/zig-datastructures/src/structures.zig:41:18: 0x103971e in test.ll (test)
try list.push('b', allocator);
^
/snap/zig/11356/lib/compiler/test_runner.zig:158:25: 0x10479b2 in mainTerminal (test)
if (test_fn.func()) |_| {
^
/snap/zig/11356/lib/compiler/test_runner.zig:35:28: 0x103dd1b in main (test)
return mainTerminal();
^
/snap/zig/11356/lib/std/start.zig:501:22: 0x103a069 in posixCallMainAndExit (test)
root.main();
^
/snap/zig/11356/lib/std/start.zig:253:5: 0x1039bd1 in _start (test)
asm volatile (switch (native_arch) {
^
???:?:?: 0x0 in ??? (???)
can you send the code of push and init?
curElement.netxt.? will crash if next is null
_ _
it seems like you're not initialising the nodes correctly...
you must've changed the code since, haven't you?
no
and while(curElement.next) can be null and skipped
pub fn push(self: *linkedList, value: u8, allocator: std.mem.Allocator) !void {
var curElement: *linkedList = self;
while (curElement.next) |next| {
curElement = next;
}
curElement.next.? = try linkedList.init(value, allocator);
}
not all that much
and init?
pub fn init(value: u8, allocator: std.mem.Allocator) !*linkedList {
var result: *linkedList = try allocator.create(linkedList);
result.data = value;
return result;
}
I see.
next is unitialized
you're not setting result.next
result.next has garbage data, and so it's both not considered null, and points to some bad place in memory
there is no need for the .? in the last line of push, you're not trying to unwrap anything, but rather set the entire optional
the ? is what gave the attempt to use null value
its removed
alright.
can you send the entire definition of linkedList? so we can go over everything?
const std = @import("std");
const expect = std.testing.expect;
const heap = std.heap;
const OutOfBounds = error{OutOfBounds};
const linkedList = struct {
data: u8 = 0,
next: ?*linkedList = null,
pub fn init(value: u8, allocator: std.mem.Allocator) !*linkedList {
var result: *linkedList = try allocator.create(linkedList);
result.data = value;
result.next = null;
return result;
}
pub fn at(self: *linkedList, i: usize) error{OutOfBounds}!linkedList {
var resultList: ?*linkedList = self;
for (0..i) |_| {
if (resultList.?.*.next == null) {
return error.OutOfBounds;
}
resultList = resultList.?.*.next;
}
return resultList.?.*;
}
pub fn push(self: *linkedList, value: u8, allocator: std.mem.Allocator) !void {
var curElement: *linkedList = self;
while (curElement.next) |next| {
curElement = next;
}
curElement.next = try linkedList.init(value, allocator);
}
};
test "ll" {
const gpalc = heap.GeneralPurposeAllocatorConfig{ .safety = true };
var gpal = heap.GeneralPurposeAllocator(gpalc){};
const allocator = gpal.allocator();
var list = try linkedList.init('a', allocator);
try list.push('b', allocator);
try expect(list.data == 'a');
if (list.at(1)) |ll| {
try expect(ll.data == 'b');
} else |e| {
std.debug.print("{any}", .{e});
}
}
a few things need improved
my return types on push and init, for example
use std.testing.allocator inside of the test block. you'll find some more bugs hiding...
so, replace the first 3 lines there with
const allocator = std.testing.allocator;
i did
it says it found a memory leak twice
one bit apart

im returning the memory leak aren't i
but in some other place you're forgetting about memory - there hides the leak
how is this leaking?
/home/kodi/Documents/projects/zig-datastructures/src/structures.zig:12:55: 0x1039197 in init (test)
var result: *linkedList = try allocator.create(linkedList);
^
/home/kodi/Documents/projects/zig-datastructures/src/structures.zig:41:35: 0x10394ad in test.ll (test)
var list = try linkedList.init('a', allocator);
that's the memory i intended to allocate?
the allocator is allocating alright - but you never free the allocated memory
the problem is that you never defined a deinit function
try to create a deinit function, that takes in a *LinkedList and a std.mem.Allocator (we expect the allocator that is passed to be the same one that was used in push and init), and frees all of the nodes from the list
its becoming a double linked list
there is no need to doublify the linked list
You only need to traverse the nodes
ig i keep a reference to the previous node and deinit behind me
exactly
You start in the root node and traverse from there foward
pub fn deinit(self: *linkedList, allocator: std.mem.Allocator) void {
var prev_node = self;
while (prev_node.next) |next| {
allocator.destroy(prev_node);
prev_node = next;
}
allocator.destroy(prev_node);
}
and if you are removing at a position, you remove it from that position and link the previous with the next one (traversing from start to finish)
this looks correct
it passes
where did you put the deinit exactly?
(the question really is - how you used defer correctly?)
right below init
const allocator = std.testing.allocator;
var list = try linkedList.init('a', allocator);
defer list.deinit(allocator);
you can look at maybe revising your at implementation (similar changes to what was done to the loop in push), and maybe-just-maybe make your names adhere to the style guide.
other than that, I think your code is complete
its complete as a very basic implementation
a lot more can be done for a linked list
drop()
from_type()
idk
you can add whatever, the current API seems correct and bug-free
I approve👍
pub fn at(self: *linkedList, i: usize) error{OutOfBounds}!*linkedList {
var resultList = self;
for (0..i) |_| {
if (resultList.next) |next| {
resultList = next;
} else {
return error.OutOfBounds;
}
}
return resultList;
}
spot on
ill make the outer facing stuff match style guide at least
rip
search replace missed anything with a star behind it
wait, this isnt
i didnt write changes before cat
lsl
the declaration of LinkedList needs to be pub, if you want to expose it: pub const LinkedList = ...
ik
it's all in file rn
it'll become pub if i decide i want to actually use it
i can't even call main without pub
const std = @import("std");
const expect = std.testing.expect;
const heap = std.heap;
const OutOfBounds = error{OutOfBounds};
const LinkedList = struct {
data: u8 = 0,
next: ?*LinkedList = null,
pub fn init(value: u8, allocator: std.mem.Allocator) !*LinkedList {
var result: *LinkedList = try allocator.create(LinkedList);
result.data = value;
result.next = null;
return result;
}
pub fn deInit(self: *LinkedList, allocator: std.mem.Allocator) void {
var prev_node = self;
while (prev_node.next) |next| {
allocator.destroy(prev_node);
prev_node = next;
}
allocator.destroy(prev_node);
}
pub fn at(self: *LinkedList, i: usize) error{OutOfBounds}!*LinkedList {
var result = self;
for (0..i) |_| {
if (result.next) |next| {
result = next;
} else {
return error.OutOfBounds;
}
}
return result;
}
pub fn push(self: *LinkedList, value: u8, allocator: std.mem.Allocator) !void {
var cur_element: *LinkedList = self;
while (cur_element.next) |next| {
cur_element = next;
}
cur_element.next = try LinkedList.init(value, allocator);
}
};
test "ll" {
//const gpalc = heap.GeneralPurposeAllocatorConfig{ .safety = true };
//var gpal = heap.GeneralPurposeAllocator(gpalc){};
const allocator = std.testing.allocator;
var list = try LinkedList.init('a', allocator);
defer list.deInit(allocator);
try list.push('b', allocator);
try expect(list.data == 'a');
if (list.at(1)) |ll| {
try expect(ll.data == 'b');
} else |e| {
std.debug.print("{any}", .{e});
}
}
pub fn main() void {
std.log.info("{any}", .{@sizeOf(LinkedList)});
}
never liked snake case
more annoying to type than camelCase
You can:
- In POSIX: export start
- In Windows: A mess but basically exporting the thing
Then you can call main from your start function
in windows was it wWinMainCRTStartup
Tho _start does have to be public 🙂
anyway, gn
its 1 am here
4am 🧠