Skip to content
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

Change meaning of underscore from actual identifier name to ignoring the value being named #4164

Open
pmwhite opened this issue Jan 12, 2020 · 6 comments
Labels
accepted This proposal is planned. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@pmwhite
Copy link

pmwhite commented Jan 12, 2020

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.

@andrewrk andrewrk added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label Jan 12, 2020
@andrewrk andrewrk added this to the 0.7.0 milestone Jan 12, 2020
@andrewrk
Copy link
Member

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.

@andrewrk andrewrk added the accepted This proposal is planned. label Jan 12, 2020
@ghost
Copy link

ghost commented Jan 13, 2020

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?

@raulgrell
Copy link
Contributor

raulgrell commented Jan 15, 2020

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, _ };

@SpexGuy
Copy link
Contributor

SpexGuy commented Apr 11, 2020

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.

@andrewrk andrewrk modified the milestones: 0.7.0, 0.8.0 Oct 9, 2020
@Vexu
Copy link
Member

Vexu commented Oct 15, 2020

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.

@andrewrk andrewrk modified the milestones: 0.8.0, 0.9.0 May 19, 2021
@andrewrk andrewrk modified the milestones: 0.9.0, 0.10.0 Nov 20, 2021
@andrewrk andrewrk modified the milestones: 0.10.0, 0.11.0 Apr 16, 2022
@andrewrk andrewrk modified the milestones: 0.11.0, 0.12.0 Apr 9, 2023
@andrewrk andrewrk modified the milestones: 0.13.0, 0.12.0 Jun 29, 2023
@hazeycode
Copy link
Contributor

hazeycode commented Jul 13, 2023

This should also work for struct and union fields:
...
// These fields cannot be assigned to or accessed.
// They only serve as padding.
...

I just wanted to add, since it's not clear from the above example, that default assignment must remain legal here and highly recommended. For example, if dealing with some opaque C API:

const Flags = packed struct(u32) {
   _: u16, // Presumably this memory is undefined, this would be a potential footgun
   some_flag: u1 = false
   _: u15 = 0, // Default assignment must remain legal for unnamed fields
};

extern fn some_c_api(flags: Flags) void; // We might not know how a library will treat the underlying integer

test {
    some_c_api(.{ .some_flag = true });
}

@andrewrk andrewrk modified the milestones: 0.14.0, 0.15.0 Feb 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepted This proposal is planned. 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

6 participants