Zig: Change meaning of underscore from actual identifier name to ignoring the value being named

Created on 13 Jan 2020  路  5Comments  路  Source: ziglang/zig

Main Proposal

This is a play stolen from functional languages like Haskell and OCaml (probably a bunch of other languages as well) where parameters to functions can be ignored by placing _ where the parameter name be. The need/usefulness for this comes about when using function pointers; the function's signature may not be ideal for that particular function and may include parameters that the function does not need to used. The motivating use-case was when providing callback functions for wayland input events.

Under this proposal, an underscore can appear in the same places that identifier introduction occurs (i.e. variable declarations, for loop variables, function parameters, etc.).

I believe this would not break any existing programs, since this proposal only expands where the underscore can be used. However, instead of only being able to use it for one parameter of a function, it could potentially be used for all of them.

==== Rest of proposal rejected ====

Debatable additional part

The first part above seems to me to be very uncontroversial and almost objectively good for the language. This second part seems to me equally reasonable, but I would be less surprised with disagreement.

For places where a type annotation is expected (like function parameters), if the variable is ignored with the underscore, then the type should be unnecessary as well. With both parts of this proposal, I could change this empty function from

fn wl_pointer_enter(data: ?*c_void, wlp: ?*wl_pointer, c: u32, wls: ?*wl_surface, a: i32, b: i32) callconv(.C) void {}

to

fn wl_pointer_enter(_, _, _, _, _, _) callconv(.C) void {}

Not only is this easier to type, I believe it makes the function more obviously trivial.

A more debatable third part

This last part is less motivated by a use-case and more motivated by making the language beautiful (which understandably may not fit into the language philosophy). Nevertheless, I do think the feature would have legitimate uses.

All of the parts together would say this:

  • An underscore may be used anywhere an identifier is used.
  • An underscore in place of a variable introduction means that whatever value the variable takes on is ignored.
  • An underscore in place of a variable use is a compile-time assertion that the variable will not actually be used and can be filled with undefined data. (Very similar to unreachable)

Above three statements seem to me to be rather symmetric and easy-to-understand.

For assignments like var abc = 5 + _; this is not very useful, but while calling functions from an unfamiliar API during prototyping, it may be useful to not need to have all the parameters that the function asks for. For example, the admittedly contrived function

fn addOrIdentity(doAdd: bool, x: i32, y: i32) i32 {
  if (doAdd) {
    return x + y;
  } else {
    return x;
  }
}

Could be called like addOrIdentity(false, 5, _); because it is known at compile-time that the value for y is not going to be used. Note that you can always pass underscore as a parameter which was defined with an underscore. That is:

fn stupid(_) void {
  std.debug.warn("stupid\n", .{});
}

stupid(_);

What are thoughts? The three sections are in the order that I think they are useful and good.

accepted proposal

Most helpful comment

This should also work for struct and union fields:

test "" {
    const S = struct {
        a: u32,
        // These fields cannot be assigned to or accessed.
        // They only serve as padding.
        _: u32,
        _: u32,
        b: u32,
    };
    var s =S{
        .a = 1,
        .b = 2,
    };
}

This does not apply to enums where you can skip values by explicitly assigning the tag value.

All 5 comments

Main proposal - accepted

Other ones- not. types are needed to make sense of whether functions are compatible with each other. The inference required to do this proposal is complicated and not beneficial enough to be worth it.

Could be called like addOrIdentity(false, 5, _); because it is known at compile-time that the value for y is not going to be used.

We have undefined for this.

So Zig already has special handling of _ for left side of assignment, and the |_| in a for/while loop. What does this proposal add? Argument names in a function definition, is that it? What about struct field names?

There's an accepted proposal for non exhaustive enums: (#2524), which would mean you couldn't use it to pad enums:

const E = enum { A, _, C, D, _ };

There's an accepted proposal for non exhaustive enums, which would mean you couldn't use it to pad enums

If #4859 is accepted and ... becomes the syntax for extensible enums, we should also consider whether this use of _ should be allowed.

This should also work for struct and union fields:

test "" {
    const S = struct {
        a: u32,
        // These fields cannot be assigned to or accessed.
        // They only serve as padding.
        _: u32,
        _: u32,
        b: u32,
    };
    var s =S{
        .a = 1,
        .b = 2,
    };
}

This does not apply to enums where you can skip values by explicitly assigning the tag value.

Was this page helpful?
0 / 5 - 0 ratings