Files
carbon-lang/proposals/p000851-variable-type-inference.md
T
Chandler CarruthandRichard Smith 94d2c1c6d4 Make proposal filenames use 6 digits and include the title (#7245)
We've talked about adding the title to the filename several times over
the years and it seems really valuable. This requires us to compute a
"slug" for the title spelling that can be part of the filename.

Beyond that, we crossed 7000 recently, and so it seems likely that we
will need to add digits sooner rather than later here, so this goes
ahead and moves us to 6 digits so we don't have to adjust again for a
reasonable length of time.

To implement this and ensure we can sustain it going forward this adds a
tool to our pre-commit that validates (and corrects if needed) the
filename.

In order to update everything and keep links working, there are a _lot_
of changes, but the most interesting for direct review are in
`proposals/scripts`.

Assisted-by: Antigravity with Gemini

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2026-05-29 00:55:04 +00:00

9.6 KiB

Variable type inference

Pull request

Table of contents

Problem

Inferring variable types is a common, and desirable, use-case. In C++, the use of auto for this purpose is prevalent. We desire this enough for Carbon that we already make prevalent use in example code.

Background

Although #826: Function return type inference introduced the auto keyword for fn, the use type inference in var should be expected to be more prevalent.

In C++, auto can be used in variables as in:

auto x = DoSomething();

In Carbon, we're already using auto extensively in examples in a similar fashion. However, it's notable that in C++ the use of auto replaces the actual type, and is likely there as a matter of backwards compatibility.

Variable type inference in other languages

#618: var ordering chose the ordering of var based on other languages. Most of these also provide inferred variable types.

Where the <identifier>: <type> syntax matches, here are a few example inferred types:

Ada appears to require a variable type in declarations.

For different syntax, Go switches from var x int = 5 to x := 5, using the := to trigger type inference.

Carbon equivalents

In Carbon, we have generally discussed:

var x: auto = 5;

However, given precedent from other languages, we could omit this:

var x = 5;

Pattern matching

As we consider variable type inference, we need to consider how pattern matching would be affected.

Swift's patterns support:

  • Value-binding patterns, as in:

    switch point {
      case let (x, y):
        ...
    }
    
  • Expression patterns, as in:

    switch point {
      case (0, 0):
        ...
      case (x, y):
        ...
    }
    
  • Combining the above, as in:

    switch point {
      case (x, let y):
        ...
    }
    

Carbon equivalents

With Carbon, we have generally discussed:

  • Value-binding patterns, as in:

    match (point) {
      case (x: auto, y: auto):
        ...
    }
    
  • Expression patterns, as in:

    match (point) {
      case (0, 0):
        ...
      case (x, y):
        ...
    }
    
  • Combining the above, as in:

    match (point) {
      case (x, y: auto):
        ...
    }
    

However, it may be possible to mirror Swift's syntax more closely; for example:

  • Value-binding patterns, as in:

    match (point) {
      case let (x, y):
        ...
    }
    
  • Expression patterns, as in:

    match (point) {
      case (0, 0):
        ...
      case (x, y):
        ...
    }
    
  • Combining the above, as in:

    match (point) {
      case (x, let y):
        ...
    }
    

In the above, this presumes to allow let inside a tuple in order to indicate the variable-binding for one member of a tuple.

Pattern matching on if

In Carbon, it's been suggested that if could support testing pattern matching when there's only one case, for example with auto:

match (point) {
  case (x, y: auto):
    // Pattern match succeeded, `y` is initialized.
  default:
    // Pattern match failed.
}

=>

if ((x, y: auto) = point) {
  // Pattern match succeeded, `y` is initialized.
} else {
  // Pattern match failed.
}

A let approach may still look like:

match (point) {
  case (x, let y):
    // Pattern match succeeded, `y` is initialized.
  default:
    // Pattern match failed.
}

=>

if ((x, let y) = point) {
  // Pattern match succeeded, `y` is initialized.
} else {
  // Pattern match failed.
}

Some caveats should be considered for pattern matching tests inside if:

  • There is syntax overlap with C++, which allows code such as:

    if (Derived* derived = dynamic_cast<Derived*>(base)) {
      // dynamic_cast succeeded, `derived` is initialized.
    } else {
      // dynamic_cast failed.
    }
    
  • Syntax is similar to match itself; it may be worth considering whether the feature is sufficiently distinct and valuable to support.

Proposal

Carbon should offer variable type inference using auto in place of the type, as in:

var x: auto = DoSomething();

At present, variable type inference will simply use the type on the right side. In particular, this means that in the case of var y: auto = 1, the type of y is IntLiteral(1) rather than i64 or similar. This may change, but is the simplest answer for now.

Open questions

Inferring a variable type from literals

Using the type on the right side for var y: auto = 1 currently results in a constant IntLiteral value, whereas most languages would suggest a variable integer value. This is something that will be considered as part of type inference in general, because it also affects generics, templates, lambdas, and return types.

Rationale based on Carbon's goals

Alternatives considered

Elide the type instead of using auto

As discussed in background, other languages allow eliding the type. Carbon could do similar, allowing:

var y = 1;

Advantages:

  • Forms a cross-language syntax consistency with languages that put the type on the right side of the identifier.
    • This is particularly strong with Kotlin and Swift, because they also use var.
  • Use of auto is currently unique to C++; Carbon will be extending its use.

Disadvantages:

  • Type inference becomes the lack of a type, rather than the presence of something different.
  • C++'s syntax legacy may have needed auto in order to provide type inference, where Carbon does not.
  • Removes syntactic distinction between binding of a new name and reference to an existing name, particularly for pattern matching. Reintroducing this distinction would likely require additional syntax, such as Swift's nested let.

We expect there will be long-term pushback over the cross-language inconsistency, particularly as the var <identifier>: auto syntax form will be unique to Carbon. However, there's a strong desire not to have the lack of a type mean the type will be inferred.

Use _ instead of auto

We have discussed using _ as a placeholder to indicate that an identifier would be unused, as in:

fn IgnoreArgs(_: i32) {
  // Code that doesn't need the argument.
}

We could use _ instead of auto, leading to:

var x: _ = 1;

However, removing auto entirely would also require using _ when inferring function return types. For example:

fn InferReturnType() -> _ {
  return 3;
}

Advantages:

  • Reduces the number of keywords in Carbon.
  • Less to type.
    • The incremental convenience may ameliorate the decision to require a keyword for type inference.

Disadvantages:

  • There's a feeling that _ means "discard", which doesn't match the semantics of inferring a return type, since the type is not discarded.
  • There may be some ambiguities in handling, such as var c: (_, _) = (a, b).
  • The reduction of typing is unlikely to address arguments that the keyword is not technically required.

The sense of "discard" versus "infer" semantics is why we are using auto.