Generic clone function

I’m trying to write a generic convert to pointer function that I can pass to convert in Mecha.

I tried

fn toPtr(allocator: std.mem.Allocator, comptime T: type) !*T {
    return allocator.dupe(T);
}

but the return type seems to be type so I get errors like

error: expected struct, enum, union, or opaque; found 'type'

Am I doing this right?

I’m trying to write a parser for a recursive grammar (see issue link below). I’m using Mecha since I didn’t find any parser generators for Zig.

I’m trying to solve this issue to no avail.

Not ready to give up and switch to Rust just yet!

The return type is type because you are duping T which is a type.
If you want to clone a value, then you also need to pass a value to the function.
Secondly dupe only works on slices. Instead you can use create and copy manually.

fn toPtr(allocator: std.mem.Allocator, comptime T: type, value: T) !*T {
    const result = try allocator.create(T);
    result.* = value;
    return result;
}
2 Likes

I thought I’d give it an ago…

Why do I get this error, though?

src/sleigh.zig:889:39: error: cannot dereference non-pointer type 'error{OutOfMemory}!*sleigh.test.toPtr.Foo'
    try testing.expectEqual(value, ptr.*);
                                   ~~~^~

Is this the right approach?

Is there a way to make it prettier or more concise?

fn toPtr(comptime T: type) fn (std.mem.Allocator, T) std.mem.Allocator.Error!*T {
    return struct {
        fn toPtr(allocator: std.mem.Allocator, value: T) std.mem.Allocator.Error!*T {
            const result = try allocator.create(T);
            result.* = value;
            return result;
        }
    }.toPtr;
}

test "toPtr" {
    const allocator = testing.failing_allocator;

    const Foo = struct {
        bar: u32,
        baz: u32,
    };

    const conv = toPtr(Foo);
    const value = Foo{ .bar = 10, .baz = 20 };
    const ptr = conv(allocator, value);
    try testing.expectEqual(value, ptr.*);
}

The error is because I’m missing a try, i.e.

const ptr = try conv(allocator, value);

Also, I was using the wrong allocator and not freeing memory, so the test should be

test "toPtr" {
    const allocator = testing.allocator;

    const Foo = struct {
        bar: u32,
        baz: u32,
    };

    const conv = toPtr(Foo);
    const value = Foo{ .bar = 10, .baz = 20 };
    const ptr = try conv(allocator, value);
    defer allocator.destroy(ptr);
    try testing.expectEqual(value, ptr.*);
}

Is there a more concise way to write toPtr, though? I tried to remove the explicit error set

fn toPtr(comptime T: type) fn (std.mem.Allocator, T) !*T {
    return struct {
        fn toPtr(allocator: std.mem.Allocator, value: T) !*T {
            const result = try allocator.create(T);
            result.* = value;
            return result;
        }
    }.toPtr;
}

but then got this error

src/sleigh.zig:868:54: error: function type cannot have an inferred error set
fn toPtr(comptime T: type) fn (std.mem.Allocator, T) !*T {

You need to explicitly specify anyerror for a function type, you can omit it when declaring a function.

I think that’s the cleanest way to write a closure currently.

1 Like

I think because you are working at comptime (the conv parameter of mecha is comptime) you could use anytype like this:

const std = @import("std");

const MemoryError = error{OutOfMemory};

fn toPtr(allocator: std.mem.Allocator, value: anytype) MemoryError!*@TypeOf(value) {
    const result = try allocator.create(@TypeOf(value));
    result.* = value;
    return result;
}

test "toPtr" {
    const t = std.testing;
    const allocator = t.allocator;

    const Foo = struct {
        bar: u32,
        baz: u32,
    };

    const value = Foo{ .bar = 10, .baz = 20 };
    const ptr = try toPtr(allocator, value);
    defer allocator.destroy(ptr);
    try t.expectEqual(value, ptr.*);

    const fptr: @TypeOf(&toPtr) = &toPtr;
    const ptr2 = try fptr(allocator, value);
    defer allocator.destroy(ptr2);
    try t.expectEqual(value, ptr2.*);
}

If you try to use a generic function as function pointer at run time (neither const nor comptime) you get an error message like this:

temp10.zig:25:15: error: variable of type '*const fn (mem.Allocator, anytype) anytype' must be const or comptime
    var fptr: @TypeOf(&toPtr) = undefined;
              ^~~~~~~~~~~~~~~
temp10.zig:25:15: note: function is generic

But it seems that you can use generic function pointers (that use anytype) when they are const or comptime, but I am not sure if I fully understand how these behave, I haven’t really used them before.

1 Like

This doesn’t work when used in a parser

❯ zig build test --summary all
test
└─ run test
   └─ zig test Debug native 1 errors
/Users/joelr/.cache/zig/p/1220b35238ee469fbef939a140f782abc024ff70cb0d8da6b9b8c20b3852991b812a/mecha.zig:68:36: error: unable to unwrap null
        .@"fn" => |f| f.return_type.?,
                      ~~~~~~~~~~~~~^~
/Users/joelr/.cache/zig/p/1220b35238ee469fbef939a140f782abc024ff70cb0d8da6b9b8c20b3852991b812a/mecha.zig:481:35: note: called from here
    const return_type = ReturnType(P);
                        ~~~~~~~~~~^~~
/Users/joelr/.cache/zig/p/1220b35238ee469fbef939a140f782abc024ff70cb0d8da6b9b8c20b3852991b812a/mecha.zig:495:32: note: called from here
) Parser(ReturnTypeErrorPayload(@TypeOf(conv))) {
         ~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~
src/sleigh.zig:49:17: note: called from here
        .convert(toPtr)

whereas this does

fn toPtr(comptime T: type) fn (std.mem.Allocator, T) anyerror!*T {
    return struct {
        fn toPtr(alloc: std.mem.Allocator, value: T) !*T {
            const result = try alloc.create(T);
            result.* = value;
            return result;
        }
    }.toPtr;
}
1 Like