Skip to content

Proposal: Add a way to quickly access a tagged union's payload, when the payload type is known at comptime #9271

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
omgitsmoe opened this issue Jul 1, 2021 · 5 comments
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@omgitsmoe
Copy link

omgitsmoe commented Jul 1, 2021

Motivation

When using zig's tagged union I often find myself in a situation where I know which tags can be active and that those tags share the same payload type. To access that payload I'd have to use a switch on the tagged union typing out all the tags that can be active, which can add up to a lot of typing if there are alot of tags sharing the same type.

(Of course you could use a bare union with a separate tag, where you wouldn't have to use a switch on the union to just access the payload directly, but you'd lose alot of the niceties that come with zig's tagged union.)

Proposal

Add a builtin to access a union's payload with a comptime known type directly.

Example

const Tagged = union(enum) {
    A: i32,
    B: []const u8,
    C: i32,
};

fn current() void {
    var t = Tagged{ .C = -5 };

    switch (t) {
        .A, .C => |*num| {
            num.* = 234;
        },
        else => unreachable,
    }
}

fn new() void {
    var t = Tagged{ .C = -5 };

    // getting a ptr to the payload
    const payload = @unionPayloadPtr(i32, t);
    // for quickly setting it
    @unionSetPayload(i32, t, 234);
}
@ghost
Copy link

ghost commented Jul 1, 2021

This should not require a new builtin.

fn unionPayloadPtr(comptime T: type, union_ptr: anytype) ?*T {
    const U = @typeInfo(@TypeOf(union_ptr)).Pointer.child;
    inline for (@typeInfo(U).Union.fields) |field, i| {
        if (field.field_type != T)
            continue;
        if (@enumToInt(union_ptr.*) == i)
            return &@field(union_ptr, field.name);
    }
    return null;
}

pub fn main() void {
    const std = @import("std");

    const Tagged = union(enum) {
        A: i32,
        B: []const u8,
        C: i32,
    };

    var t = Tagged{ .C = -5 };

    if (unionPayloadPtr(i32, &t)) |ptr|
        std.debug.warn("{}\n", .{ptr.*});

    if (unionPayloadPtr(i32, &t)) |ptr|
        ptr.* = 100;
}

This takes advantage of the fact that you can use struct-like field access in a union (which asserts if you don't access the active tag). I don't think this is documented. E.g. you could write t.A to get the payload of the A variant, assuming it's the active tag. In my example I'm doing it via the @field builtin.

@omgitsmoe
Copy link
Author

Thank you! Amazing that this already works.

Should I leave this open for maybe adding sth. like this to std.meta?

@omgitsmoe
Copy link
Author

Am I doing sth. wrong or why is there an extra (compared to the switch/old version) cmp instruction in the new2 version on godbolt?

@ifreund
Copy link
Member

ifreund commented Jul 2, 2021

Am I doing sth. wrong or why is there an extra (compared to the switch/old version) cmp instruction in the new2 version on godbolt?

You're not doing anything wrong afaict. This use-case could benefit from the proposed and accepted inline switch (#7224) which would allow writing this function with semantics that are much easier for the optimizer to deal with.

@LewisGaul
Copy link
Contributor

Should I leave this open for maybe adding sth. like this to std.meta?

+1 from me for something in std.meta for this. I find myself copying the logic in

inline for (info.fields) |u_field| {

which feels like it could be more suited to std.meta, providing a simpler option like a unionPayload() function.

@Vexu Vexu added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label Aug 6, 2021
@Vexu Vexu modified the milestones: 0.10.0, 0.9.0 Aug 6, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests

5 participants