- Problem
- Background
- Proposal
- Open questions
- Rationale based on Carbon's goals
- Alternatives considered
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.
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.
#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.
In Carbon, we have generally discussed:
var x: auto = 5;
However, given precedent from other languages, we could omit this:
var x = 5;
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): ... }
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.
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.
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.
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.
- Code that is easy to read, understand, and write
- Frequently code will have the type on the line, as in
var x: auto = CreateGeneric[Type](args)
. Avoiding repetition of the type will reduce the amount of code that must be read, and should allow readers to comprehend more quickly.
- Frequently code will have the type on the line, as in
- Interoperability with and migration from existing C++ code
- The intent is to have
auto
work similarly to C++'s type inference, in order to ease migration.
- The intent is to have
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
.
- This is particularly strong with Kotlin and Swift, because they also use
- 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.
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
.