Idiomatic way to wite/pass type to generic function in ziglang - zig

This is a much simplified version of my real issue but hopefully demonstrates my question in a concise manner.
My question is about the interface to printKeys. I have to pass in the type of the data to be printed as a comptime parameter, and the easiest way to get this is to use #TypeOf on the map at the point of calling it.
Coming from C++ this seems slightly inelegant that the type can't be inferred, although I do like being explicit too.
Is there a more idiomatic way to have a generic function in zig that doesn't need this use of #TypeOf at the point of calling, or a better way to do this in general?
const std = #import("std");
fn printKeys(comptime MapType: type, data: MapType) void {
var iter = data.keyIterator();
while (iter.next()) | value | {
std.debug.print("Value is {}\n", .{value.*});
}
}
pub fn main() !void {
const allocator = std.heap.page_allocator;
var map = std.AutoHashMap(i32, []const u8).init(allocator);
defer map.deinit();
try map.put(10, "ten");
try map.put(12, "twelve");
try map.put(5, "five");
printKeys(#TypeOf(map), map);
}

use anytype. you can find more examples in zig docs (Ctrl + F) and search anytype
fn printKeys(data: anytype) void {
...
}
pub fn main() !void {
...
printKeys(map);
}

Related

How can I create a HashMap on the heap in Zig?

The conventional way to make an object on the heap is to make a create fn:
const Something = struct {
a:Allocator,
b:[1000]u8,
pub fn create(a:Allocator) !*Something {
var mem = try a.create(Something)
mem.* = {
.a =a,
.b = undefined
};
return mem;
}
}
But what about if I want to put a std lib ArrayHashMap on the heap? For example:
const StringStringArrayHashMap = std.StringArrayHashMap([]const u8);
fn makeMap(a:Allocator) StringStringArrayHashMap {
return StringStringArrayHashMap.init(a);
}
const WithMap = struct {
map:StringStringHashMap
};
fn fillMap(a:Allocator) !WithMap {
var map = makeMap(a);
try map.put("a", "hello");
try map.put("b", "world");
return WithMap { .map = map };
}
fn badMemory(a:Allocator) !void {
const with_map = fillMap(a);
_ = with_map;
}
badMemory will receive a WithMap but it's internal map, having been made on the stack in fillMap will be freed at the end of fillMap and consequently unsade in badMemory.
I can't see any way to make a valid HashMap without somehow hacking the internals of the zig stdlib.
You can put the map on the heap the same way you did with Something type:
var map: *StringStringArrayHashMap = try allocator.create(StringStringArrayHashMap);
map.* = StringStringArrayHashMap.init(allocator);
badMemory will receive a WithMap but it's internal map, having been made on the stack in fillMap will be freed at the end of fillMap and consequently unsafe in badMemory.
This is false. The map is safe to use in badMemory as it's copied to the stack in badMemory. (Or maybe the compiler could decide to pass it as a pointer, I'm not sure if parameter's pass-by-value rule applies to the return value. Doesn't matter here.) But you should probably be careful when copying the map or you might step into the same issue as this question.

Could someone explain how this works: const print = #import("std").debug.print;

How can this const print variable behave like a function?
const print = #import("std").debug.print;
print("hello, world", .{});
I understand you can assign expressions to variables. But this seems to behave like a precompiler macro in c/c++, I wouldn't have guessed that.
Is it because "all variables declared in a comptime expression are implicitly comptime variables" and # makes it a comptime expression, so it is evaluated before compilation, much like a macro would in c?
Could someone elaborate a bit? This seems a very powerful feature.
# does not indicate a comptime expression; rather # prefixes built-in functions in Zig. #import returns a struct that provides access to the declarations made public by the imported file.
The expression #import("std").debug.print evaluates to the print function defined in the standard library file std/debug.zig; it is not the expression that is assigned to print in the posted code, but the function. That is, the posted code works because print in OP code is actually a function. This can be seen by running the code below:
const print = #import("std").debug.print;
pub fn main() void {
print("#TypeOf print: {}\n", .{ #TypeOf(print) });
}
Results:
$ zig run print_type.zig
#TypeOf print: fn(comptime []const u8, anytype) void
Function Assignment
OP has asked for another example of assigning a function that is not imported to an identifier:
const print = #import("std").debug.print;
fn my_function(msg: []const u8) void {
print("{s}\n", .{ msg });
}
const my_function_alias = my_function;
pub fn main() void {
const another_function_alias = my_function;
const yet_another_function_alias = my_function_alias;
my_function("mf");
my_function_alias("mfa");
another_function_alias("afa");
yet_another_function_alias("yafa");
}
Program output:
$ zig run function_assignment.zig
mf
mfa
afa
yafa

How do I pass a stream or writer parameter to a function in Zig?

I'm trying to pass the output stream to a function but can't get it right. This sample code shows a couple of the things I've tried
// Attempts to pass stream or writer to a function
const std = #import("std");
pub fn main() !void {
// #1
try print1(std.io.getStdOut(), "Hello, ");
// #2
try print2(std.io.getStdOut().writer(), "world!");
}
// error: 'File' is not marked 'pub'
pub fn print1(file: std.io.File, str: []const u8) !void
{
try file.writer().print("{s}", .{str});
}
// error: expected type 'type', found 'fn(comptime type, comptime type, comptime anytype) type'
fn print2(writer: std.io.Writer, str: []const u8) !void
{
try writer.print("{s}", .{str});
}
I'm using Zig 0.10.0
The call to std.io.getStdOut() returns a File, but the File type is in the std.fs namespace. Calling std.io.getStdOut().writer() returns a Writer from the std.fs.File namespace. You could also declare the writer parameter with the anytype keyword to get type inference at the time of the function call.
Here is a modified version of OP posted code:
const std = #import("std");
pub fn main() !void {
const stdout = std.io.getStdOut();
const writer = stdout.writer();
// #1
// Pass `stdout` to a function:
try print1(stdout, "Hello, ");
// #2
// Pass a `Writer` to a function:
try print2(writer, "world!\n");
// #3
// Pass a `Writer` to a function:
try print3(writer, "Hello, again!\n");
}
fn print1(file: std.fs.File, str: []const u8) !void {
try file.writer().print("{s}", .{str});
}
// Explicit type annotation for `writer`:
fn print2(writer: std.fs.File.Writer, str: []const u8) !void {
try writer.print("{s}", .{str});
}
// The type of `writer` is inferred when the function is called:
fn print3(writer: anytype, str: []const u8) !void {
try writer.print("{s}", .{str});
}
And a sample run:
$ zig run print_stream.zig
Hello, world!
Hello, again!
io.Writer is a generic data structure. I.e. it's a function that returns a type. You cannot use it as a function argument, but you can:
Use anytype.
Use an alias, like fs.File.Writer.
Use a "proxy" type, like fs.File, on which you'll call writer().
Use full specialization, like io.Writer(fs.File, fs.File.WriteError, fs.File.write).
anytype is required for functions that must accept arbitrary writers. Otherwise, it might be nicer to use an alias or a "proxy" type.

Calling GMP from Zig

I'm new to Zig and wanted to use the cImport to call out to the GNU Multiprecision library GMP. I got this to work but it feels awkward because Zig apparently doesn't cast from arrays to pointers by default and GMP uses this approach to pass by reference. Is there a cleaner approach than mine? Are there existing examples of Zig calling GMP?
const gmp = #cImport({
#cInclude("gmp.h");
});
pub fn main() void {
var x: gmp.mpz_t = undefined;
gmp.mpz_init(&x[0]);
gmp.mpz_set_ui(&x[0], 7);
gmp.mpz_mul(&x[0], &x[0], &x[0]);
_ = gmp.gmp_printf("%Zd\n", x);
}
You should be able to drop the [0]:
const gmp = #cImport({
#cInclude("gmp.h");
});
pub fn main() void {
var x: gmp.mpz_t = undefined;
gmp.mpz_init(&x);
gmp.mpz_set_ui(&x, 7);
gmp.mpz_mul(&x, &x, &x);
_ = gmp.gmp_printf("%Zd\n", x);
}

Does Zig support the union of anonymous structs and arrays?

Is this possible in zig? And if so how would something like the following C++ code look in Zig?
template<class T>
class vec3
{
union
{
struct{ T x,y,z; };
T vec_array[3];
};
};
No, there is no such thing supported in Zig, and it is unlikely there ever will be. The closest you'll get is something like this:
const std = #import("std");
pub fn Vec3(comptime T: type) type {
return extern union {
xyz: extern struct { x: T, y: T, z: T },
arr: [3]T,
};
}
test {
var v3: Vec3(u32) = .{ .arr = .{ 42, 73, 95 } };
try std.testing.expectEqual(v3.arr[0], v3.xyz.x);
try std.testing.expectEqual(v3.arr[1], v3.xyz.y);
try std.testing.expectEqual(v3.arr[2], v3.xyz.z);
}
where the extern qualifiers here make the containers behave in accordance with the C ABI (and in the case of the union, makes it defined behavior to access any member).
See this comment under proposal #985 for a fairly comprehensive rationale on as to why "anonymous field nesting" was rejected.

Resources