Unused Pattern Bindings (Unused Function Parameters)

[Pull request](https://github.com/carbon-language/carbon-lang/pull/2022

Table of contents

Abstract

This proposal specifies how unused pattern bindings are written in the Carbon programming language. This is a more general problem statement of “how do users specify unused function parameters” as function parameter declarations are a more specific form of pattern.

Related issue: #1996.

Problem

“How does a user of Carbon declare an unused pattern binding?”

Given that function parameters are a specific type of pattern binding, a more specific question that will have the same answer is “How does a user of Carbon declare an unused function parameter?”

Bindings that can be specified as unused makes code explicit and unambiguous. Authors of code can clearly state that a value is not intended to be used. Tools such as compilers or linters can explicitly handle a parameter that is specified as unused, but later used, or a parameter that is unused despite not being specified as unused.

Background

See the overall design for the current state of things with regards to name bindings and the underscore character, _.

See issue #476: Optional argument names (unused arguments) for the previous conversation on this topic.

Note: These are not exhaustive lists and not intended to be thoroughly investigated, rather a brief overview of some prior art.

C and C++ style guides, linters, and other tools have addressed unused function parameters specifically with varying levels of clarity for readers.

More generally, various languages use the underscore character (_) to signal an unused binding or pattern.

Proposal

Users can explicitly declare a value in a pattern as unused in two forms:

  • _: i32 : Using the underscore token, _, in place of a name.

  • unused size: i32 : Using the leading unused keyword followed by the name.

Details

Introducing two syntaxes satisfies the desire to have a terse way to discard values, but still provides authors with a more verbose, explicit syntax that preserves the name.

Both approaches are unambiguous – to human readers and authors as well as programmatic interpretations. The inclusion of an explicit unused keyword allows authors to preserve the name of a value for documentation purposes, while still explicitly marking the value as discarded in an interpretable way to humans and programs alike.

The unused keyword would be a new keyword in Carbon. This keyword would only be valid when preceding a name in a pattern binding and the keyword would tightly bind to the following name, disallowing specifying an entire sub-pattern as unused.

The behavior of unused name bindings

Names that are qualified with the unused keyword are visible for name lookup but uses are invalid, including when they cause ambiguous name lookup errors. If attempted to be used, a compiler error will be shown to the user, instructing them to either remove the unused qualifier or remove the use.

The inverse, where a name is not qualified by the unused qualifier, but never used, will cause the compiler to emit a warning diagnostic, informing the user that a given name was not used and suggesting to either remove the binding or mark it as unused.

Examples

Examples with an unused function parameter

// Function declaration (may be in API file)
fn Sum(x: List(i32), size: i32) -> i32;

// Implementation that doesn't use the size parameter
fn Sum(x: List(i32), _: i32) -> i32 { ... }
// or:
fn Sum(x: List(i32), unused size: i32) -> i32 { ... }

Examples with an unused variable

fn Bar() -> (i32, i32);
fn Foo() -> i32 {
  var (x: i32, _: i32) = Bar();
  // or:
  var (x: i32, unused y: i32) = Bar();

  return x;
}

Examples with an unused case binding

fn Bar() -> (i32, i32);
fn Foo() -> i32 {
  match (Bar()) {
    case (42, y: i32) => {
      return y;
    }
    case (x: i32, _: i32) => {
      return x;
    }
    // or:
    case (x: i32, unused y: i32) => {
      return x;
    }
  }
}

Rationale

  • This proposal supports the Carbon goal of having Code that is easy to read, understand, and write
    • Carbon should not use symbols that are difficult to type, see, or differentiate from similar symbols in commonly used contexts.
    • Syntax should be easily parsed and scanned by any human in any development environment, not just a machine or a human aided by semantic hints from an IDE.
    • Explicitness must be balanced against conciseness, as verbosity and ceremony add cognitive overhead for the reader, while explicitness reduces the amount of outside context the reader must have or assume.

This proposal uses the underscore, _, character to denote an unused value, a meaning used across various other programming languages. A lone underscore character only has a single meaning in Carbon and will be unambiguous across contexts.

Both syntaxes are easily read by humans, either by seeing the _ character alone, or by introducing a keyword that allows code to read naturally such as unused size : i32.

The inclusion of two syntaxes allows authors to decide when they will favor conciseness or explicitness.

Alternatives considered

Commented names

C++ allows function parameters to be unnamed, allowing function declarations such as

int Foo(int) { ... }
int Foo(int /*unused_name*/) { ... }

Advantages:

  • Consistency with C++

Disadvantages:

  • Carbon does not intend to support /* */ comments, so this option is effectively a non-starter

Only short form support with _

Carbon could provide only a single way to discard a value with the underscore, _, token

// Function declaration (may be in API file)
fn Sum(x: List(i32), size: i32) -> i32;

// Implementation that doesn't use the size parameter
fn Sum(x: List(i32), _: i32) -> i32 { ... }

Advantages:

  • A smaller language, one less keyword, and a single way to write code

Disadvantages:

  • Less expressiveness, less documentation through names

Named identifiers prefixed with _

Carbon could treat identifiers prefixed with _ as unused identifiers and discard their names.

// Function declaration (may be in API file)
fn Sum(x: List(i32), size: i32) -> i32;

// Implementation that doesn't use the size parameter
fn Sum(x: List(i32), _size: i32) -> i32 { ... }

Advantages:

  • Reuse of the underscore character in both short and long form bindings
  • Functionally similar to commented names in C++

Disadvantages:

  • Tying the semantics of a name being unused to the particular spelling of the identifier (starting with a _ character), even while it remains an identifier seems more subtle and indirect than necessary.
    • We shouldn’t use identifier spellings as a side-channel for semantic information.
    • Semantics, including used versus unused, should be conveyed in an orthogonal manner to the name rather than tying them together.
  • Would require the name to change when shifting between being used or unused, which could undermine some of its utility such as remaining for annotations, metaprogramming, or diagnostics.

Anonymous, named identifiers

A potential syntax for naming unused bindings while retaining the underscore, _, token is an optional name suffix following the underscore token.

// Function declaration (may be in API file)
fn Sum(x: List(i32), size: i32) -> i32;

// Implementation that doesn't use the size parameter
fn Sum(x: List(i32), _ size: i32) -> i32 { ... }

Advantages:

  • Reuse of the underscore token in both short and long form bindings

Disadvantages:

  • The underscore token, while consistent, may be less human readable than a dedicated keyword such as unused

Attributes

While attributes aren’t designed yet, there’s a possibility that in the future, the Carbon language will have some mechanism to attach metadata to parts of source code to inform readers, compilers, and other tools. Its conceivable that there could be an unused attribute which could be use to implement similar semantics as the proposed unused keyword.

Advantages:

  • Opens the inspection and subsequent actions taken to whatever ecosystem and programmatic support is introduced by attributes.
  • Solves a specific problem with a generic approach
    • Removes the introduction of a new keyword

Disadvantages:

  • Given the current proposal where unused bindings specify unambiguous, absolute behavior for the compiler’s handling of names, similar to the private keyword, using attributes as the transport for this semantic information is indirect.

Given that attributes are not designed and may go in a number of unknown directions, it might be worth revisiting this option once attributes are fully designed.