Unintentional Temporary with anytype

Anytype can deduce both pointers and direct values when making function calls. It’s important to provide anytype parameters with the types you expect should be provided.


Mixing Pointer and Value Types

Let’s create a custom type that has an array of integers.

const MyType = struct {
    array: [42]usize = undefined, // or some more sensible default
};

Now, let’s make a generic function that could be called on MyType:

pub fn foo(arg: anytype) void {
    for(&arg.array) |elem| {
        // do something with the element
    }
}

We can invoke this function in two ways:

const bar: MyType = .{};

// takes in bar by value... potentially unintentional temporary
foo(bar);

// takes in bar by pointer - no direct value copy
foo(&bar);

Here we can see that foo will not complain about arg either way. Whether a temporary is created not doesn’t matter to foo.

Note that it is possible that the optimizer may decide to pass by reference anyway on higher optimization levels. However, optimizers are not a substitute for properly writing and understanding your code.


Is this always a footgun?

Zig’s pointer semantics allow us to directly call through the pointer into the members of a type. This can be useful if temporaries are not a problem or would be discarded immediately after making a call. The question is whether or not you are aware that this issue can occur.


Workarounds

Using @typeInfo can be very useful in this case to determine if you are dealing with a pointer or a value. If (for example) you only expect pointers, consider adding a @compileError in the case where your assumption has been violated.

You can also further parameterize based on @typeInfo.

switch (@typeInfo(@TypeOf(arg))) {
    .Pointer => { 
        // expects a pointer type
        const T = std.meta.Child(@TypeOf(arg));
        // do more stuff with T...
    },
    else => {
        // anything else...
        const T = @TypeOf(arg);
        // do more stuff with T...
    }
};

Summary

Be aware of the types you give to anytype parameters because they may pass the duck-test, but they can still violate your assumptions.

For more information on using anytype, see: Generic Programming and anytype

8 Likes