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.
ifIn 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.
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.auto work similarly to C++'s type inference, in
order to ease migration.autoAs discussed in background, other languages allow eliding the type. Carbon could do similar, allowing:
var y = 1;
Advantages:
var.auto is currently unique to C++; Carbon will be extending its use.Disadvantages:
auto in order to provide type
inference, where Carbon does not.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.
_ instead of autoWe 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:
Disadvantages:
_ means "discard", which doesn't match the
semantics of inferring a return type, since the type is not discarded.var c: (_, _) = (a, b).The sense of "discard" versus "infer" semantics is why we are using auto.