Why does my tree creation fail without the use of inline? - zig

I'm trying to create a Trie structure in Zig using Zigs StringHashMap.
I am able to get it to work a bit, but only by using a "inline" for loop which is not really usable as this requires the paths to be known at compile time :-(
Any help/explanation would be much appreciated :-)
The code:
const std = #import("std");
const Allocator = std.mem.Allocator;
const print = std.debug.print;
const expect = std.testing.expect;
const HashMap = struct {
value: u8,
children: std.StringHashMap(*HashMap),
};
fn newHashMap(allocator: Allocator, value: u8) HashMap {
return HashMap{
.value = value,
.children = std.StringHashMap(*HashMap).init(allocator),
};
}
fn showTree(root: *std.StringHashMap(*HashMap), keys:[3][]const u8 ) void {
var hashMap = root;
for (keys) |key| {
print("get key {s}\n", .{key});
var value = hashMap.get(key);
if (value) |node| {
print("we got a value for {s}:{}\n", .{key,node.value});
hashMap = &node.children;
} else {
print("no value for {s}\n", .{key});
break;
}
}
}
test "HashMap" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const gpaAllocator = gpa.allocator();
var arena = std.heap.ArenaAllocator.init(gpaAllocator);
defer {
arena.deinit();
const leaked = gpa.deinit();
if (leaked) expect(false) catch #panic("TEST FAIL"); //fail test; can't try in defer as defer is executed after we return
}
const allocator = arena.allocator();
var root = &std.StringHashMap(*HashMap).init(allocator);
var hashMap = root;
const keys = [_][]const u8{ "a", "b", "c" };
const values: [3]u8 = .{ 1, 2, 3 };
// create tree
inline for (keys) |key, i| {
print("put key {s}:{}\n", .{ key, values[i] });
var newNode = newHashMap(allocator, values[i]);
try hashMap.put(key, &newNode);
showTree(root,keys);
hashMap = &newNode.children;
}
showTree(root,keys);
}
This prints:
Test [1/1] test "HashMap"...
put key a:1
put key b:2
put key c:3
get key a
we got a value for a:1
get key b
we got a value for b:2
get key c
we got a value for c:3
All 1 tests passed.
as expected.
Removing the 'inline' results in:
Test [1/1] test "HashMap"...
put key a:1
put key b:2
put key c:3
get key a
we got a value for a:3
get key b
no value for b
All 1 tests passed.

The answer turned out to be quite obvious (with hindsight ;-)) as mentioned in 1:
var declarations inside functions are stored in the function's stack frame. Once a function returns, any Pointers to variables in the function's stack frame become invalid references, and dereferencing them becomes unchecked Undefined Behavior.
This explains the strange behaviour in a loop without inline.
The pointers just get overwritten resulting in Undefined Behaviour.
By adding 'inline' the loop is unwound and then there is no pointer reuse, hence the correct output.
The correct way of dealing with this is to allocate the struct explicitly and pass around the pointer to the struct as shown in 2.
Once that is sorted it all makes sense.
https://ziglang.org/documentation/master/#Where-are-the-bytes
https://www.reddit.com/r/Zig/comments/s6v8t3/idiomatic_zig_for_initializing_an_allocated/
For reference, the working code without 'inline' below:
const std = #import("std");
const Allocator = std.mem.Allocator;
const print = std.debug.print;
const expect = std.testing.expect;
const HashMap = struct {
value: u8,
children: std.StringHashMap(*HashMap),
};
fn newHashMap(allocator: Allocator, value: u8) !*HashMap {
const node = try allocator.create(HashMap);
node.* = .{
.value = value,
.children = std.StringHashMap(*HashMap).init(allocator),
};
return node;
}
fn showTree(root: *std.StringHashMap(*HashMap), keys:[3][]const u8 ) void {
var hashMap = root;
for (keys) |key| {
print("get key {s}\n", .{key});
var value = hashMap.get(key);
if (value) |node| {
print("we got a value for {s}:{}\n", .{key,node.value});
hashMap = &node.children;
} else {
print("no value for {s}\n", .{key});
break;
}
}
}
test "HashMap" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const gpaAllocator = gpa.allocator();
var arena = std.heap.ArenaAllocator.init(gpaAllocator);
defer {
arena.deinit();
const leaked = gpa.deinit();
if (leaked) expect(false) catch #panic("TEST FAIL"); //fail test; can't try in defer as defer is executed after we return
}
const allocator = arena.allocator();
var root = &std.StringHashMap(*HashMap).init(allocator);
var hashMap = root;
const keys = [_][]const u8{ "a", "b", "c" };
const values: [3]u8 = .{ 1, 2, 3 };
// create tree
for (keys) |key, i| {
print("put key {s}:{}\n", .{ key, values[i] });
var newNode = try newHashMap(allocator, values[i]);
try hashMap.put(key, newNode);
hashMap = &newNode.children;
}
showTree(root,keys);
}

Related

Looping over an integer range in Zig

Is a while-loop like this the idiomatic way to loop over an integer range in Zig?
var i: i32 = 5;
while (i<10): (i+=1) {
std.debug.print("{}\n", .{i});
}
I first tried the python-like
for (5..10) |i| {
// ....
but that doesn't work.
zig has no concept of integer range loops but there's a hack by nektro which create a random []void slice, so you can iterate with for loop
const std = #import("std");
fn range(len: usize) []const void {
return #as([*]void, undefined)[0..len];
}
for (range(10)) |_, i| {
std.debug.print("{d}\n", .{i});
}

The return type 'int?' isn't a 'int', as required by the closure's contex

The following:
var sortedByValue = SplayTreeMap<int, String>.from(
fruit, (key1, key2) => fruit[key1].compareTo(fruit[key2]));
complains about null safety, then I add "?" to fruit[key1]?, ok then I.... ahhh?
import 'dart:collection';
void splayTreeMapExample(){
var fruit = SplayTreeMap<int, String>();
fruit[0] = 'Banana';
fruit[5] = 'Plum';
fruit[6] = 'Strawberry';
fruit[2] = 'Orange';
fruit[3] = 'Mango';
fruit[4] = 'Blueberry';
fruit[1] = 'Apple';
print(fruit);
fruit.forEach((key, val) {
print('{ key: $key, value: $val}');
});
var sortedByValue = SplayTreeMap<int, String>.from(
fruit, (key1, key2) => fruit[key1]?.compareTo(fruit[key2]));
print(sortedByValue);
}
You got to love null safety(Null safety principles - Non-nullable by default, hmm, right - well it must be the "Map/fruit" return value):
fruit, (key1, key2) => fruit[key1]!.compareTo(fruit[key2]!));

Search ArrayList of Structs in zig

I expect this is a question with a very simple answer about how to do this well in zig.
I want to search an ArrayList of some struct to find a record by one of the fields.
In C++ I would consider using std::find_if and a lambda but there doesn't seem to be anything like this in the zig standard library unless I missed something.
Is there a better / more idiomatic way than the simple loop like below?
const std = #import("std");
const Person = struct {
id: i32,
name: []const u8
};
pub fn main() !void {
const allocator = std.heap.page_allocator;
var data = std.ArrayList(Person).init(allocator);
defer data.deinit();
try data.append(.{.id = 1, .name = "John"});
try data.append(.{.id = 2, .name = "Dave"});
try data.append(.{.id = 8, .name = "Bob"});
try data.append(.{.id = 5, .name = "Steve"});
// Find the id of the person with name "Bob"
//
// -- IS THERE A BETTER WAY IN ZIG THAN THIS LOOP BELOW? --
//
var item_index: ?usize = null;
for (data.items) | person, index | {
if (std.mem.eql(u8, person.name, "Bob")) {
item_index = index;
}
}
std.debug.print("Found index is {}\n", .{item_index});
}
There's not that many built-in utilities present in stdlib, indeed. However, for that piece of code, you may declare the found index as a const:
const item_index = for (data.items) |person, index| {
if (std.mem.eql(u8, person.name, "Bob")) break index;
} else null;

Why am I getting this NoSuchMethodError?

I have this code
String encrypt(String x) {
String out;
var _x = x.codeUnits;
List dict;
/* <dict_assignment> */
dict[0] = 'a';
dict[1] = 'b';
dict[2] = 'c';
dict[3] = 'd';
dict[4] = 'e';
dict[5] = 'f';
dict[6] = 'g';
dict[7] = 'h';
dict[8] = 'i';
dict[9] = 'j';
/* </dict_assignment> */
_x.toList().forEach((i) {
var _i = i.toString();
_i.split("").forEach((k) {
var _k = int.parse(k);
print(_k);
print(dict[_k]);
out += dict[_k];
});
});
return out;
}
(Yes I'm writing HTML tags as comments in Dart...sue me)
(Idk why my indentations are messed up)
For some reason when I use this same function with a random string like this
var x = encrypt("hmm interesting");
I keep getting this
Unhandled exception:
NoSuchMethodError: The method '[]=' was called on null.
Receiver: null
Tried calling: []=(0, "a")
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:54:5)
Please help me I'm actually confused why this is happening
You have not initialized your dict variable, so it contains null.
If you change List dict; to List dict = []; then that would start working.
You also haven't initialized out.
The remainder of the code is leaning towards being overly complicated, and can be optimized as well. Here is a suggestion:
String encrypt(String x) {
var out = StringBuffer();
const dict = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
for (var i in x.codeUnits) { // x.codeUnits is a list. Use for-in to iterate it.
for (var k in i.toString().codeUnits) {
var _k = k ^ 0x30; // Best way to convert code unit for 0-9 into integer 0-9.
// print(_k);
// print(dict[_k]);
out.write(dict[_k]); // Use a StringBuffer instead of repeated concatenation.
}
}
return out.toString();
}
It does not appear to be a decryptable encryption. The string "77" and the string "ᖳ" (aka "\u15b3") both encrypt to "ffff".
Or, if you want to "code-golf" rather than be readable or close to the original, it can also be a one-liner:
String encrypt(String x) => [
for (var i in x.codeUnits)
for (var k in "$i".codeUnits) "abcdefghij"[k ^ 0x30]
].join("");

need Dart mirror instantiate() function

to add some sanity to my life, looking for instantiate() function as syntactic sugar to Dart's mirror library: instantiate( class|type|instance, argArray )
class Klass {
int i1;
Klass( int i1 ) {
this.i1 = (i1 is int) ? i1 : 0;
}
}
type ktype = Klass;
Klass kinstance = new Klass( 5 );
Klass test1 = instantiate( Klass, [5] );
Klass test2 = instantiate( ktype, [5] );
Klass test3 = instantiate( kinstance, [5] );
currently 90% of my interaction with mirrors would be covered by this one function. currently blindly cutting and copying out of sheer stupidity. certainly someone smarter than me has done this already!
here is instantiate( type, [constructor, positional, named] ) for all occassions:
arguments of constructor, positional and named are all optional
type can be Type, in instantiated type, or a string representation of the type
constructor: eg, new Map.from(...) - 'from' is the constructor, either 'from' or #from
positional: positional arguments in a List
named: names arguments in Map, keys may be 'key' or #key
dynamic instantiate( dynamic v_type, [dynamic v_constructorName, List v_positional, Map v_named] ) {
Type type =
( _type is Type ) ? v_type
: (v_type is String ) ? str2Type( v_type )
: reflect(v_type).type.reflectedType;
Map v_named2 =
(v_named is Map) ? v_named
: (v_positional is Map) ? v_positional
: (v_constructorName is Map) ? v_constructorName
: {};
Map named = {};
v_named2.keys.forEach( (k) => named[(k is Symbol)?k:new Symbol(k)] = v_named2[k] );
List positional =
(v_positional is List) ? v_positional
: (v_constructorName is List) ? v_constructorName : [];
Symbol constructorName =
(v_constructorName is Symbol) ? v_constructorName
: (v_constructorName is String) ? Symbol(v_constructorName)
: const Symbol('');
return reflectClass(type).newInstance(constructorName, positional, named).reflectee;
}
import 'dart:mirrors';
void main() {
Type ktype = Klass;
Klass kinstance = new Klass( 5 );
// Constructor name
var ctor = const Symbol("");
Klass test1 = instantiate(Klass, ctor, [1]);
Klass test2 = instantiate(ktype, ctor, [2]);
Klass test3 = instantiate(reflect(kinstance).type.reflectedType, ctor, [3]);
Klass test4 = instantiate(Klass, #fromString, ["4"]);
print(test1.i1);
print(test2.i1);
print(test3.i1);
print(test4.i1);
}
dynamic instantiate(Type type, Symbol constructorName, List positional, [Map named]) {
return reflectClass(type).newInstance(constructorName, positional, named).reflectee;
}
class Klass {
int i1;
Klass( int i1 ) {
this.i1 = (i1 is int) ? i1 : 0;
}
Klass.fromString(String i) {
i1 = int.parse(i, onError : (s) => i1 = 0);
}
}
Output:
1
2
3
4

Resources