if expressionsAn if expression is an expression of the form:
ifconditionthenvalue1elsevalue2
The condition is converted to a bool value in the same way as the condition
of an if statement.
Note: These conversions have not yet been decided.
The value1 and value2 are implicitly converted to their
common type, which is the type of the if expression.
if expressions have very low precedence, and cannot appear as the operand of
any operator, except as the right-hand operand in an assignment. They can appear
in other context where an expression is permitted, such as within parentheses,
as the operand of a return statement, as an initializer, or in a
comma-separated list such as a function call.
The value1 and value2 expressions are arbitrary expressions, and can
themselves be if expressions. value2 extends as far to the right as
possible. An if expression can be parenthesized if the intent is for value2
to end earlier.
// OK, same as `if cond then (1 + 1) else (2 + (4 * 6))`
var a: i32 = if cond then 1 + 1 else 2 + 4 * 6;
// OK
var b: i32 = (if cond then 1 + 1 else 2) + 4 * 6;
An if keyword at the start of a statement is always interpreted as an
if statement, never as an if
expression, even if it is followed eventually by a then keyword.
The converted condition is evaluated. If it evaluates to true, then the
converted value1 is evaluated and its value is the result of the expression.
Otherwise, the converted value2 is evaluated and its value is the result of
the expression.
The common type of two types T and U is (T as CommonType(U)).Result, where
CommonType is the Carbon.CommonType constraint. CommonType is notionally
defined as follows:
constraint CommonType(U:! CommonTypeWith(Self)) {
extend CommonTypeWith(U) where .Result == U.Result;
}
The actual definition is a bit more complex than this, as described in symmetry.
The interface CommonTypeWith is used to customize the behavior of
CommonType:
interface CommonTypeWith(U:! Type) {
let Result:! Type
where Self is ImplicitAs(.Self) and
U is ImplicitAs(.Self);
}
The implementation A as CommonTypeWith(B) specifies the type that A would
like to result from unifying A and B as its Result.
Note: It is required that both types implicitly convert to the common type.
Some blanket impls for CommonTypeWith are provided as part of the prelude.
These are described in the following sections.
Note: The same mechanism is expected to eventually be used to compute common types in other circumstances.
The common type of T and U should always be the same as the common type of
U and T. This is enforced in two steps:
SymmetricCommonTypeWith interface implicitly provides a
B as CommonTypeWith(A) implementation whenever one doesn't exist but an
A as CommonTypeWith(B) implementation exists.CommonType is defined in terms of SymmetricCommonTypeWith, and requires
that both A as SymmetricCommonTypeWith(B) and
B as SymmetricCommonTypeWith(A) produce the same type.The interface SymmetricCommonTypeWith is an implementation detail of the
CommonType constraint. It is defined and implemented as follows:
interface SymmetricCommonTypeWith(U:! Type) {
let Result:! Type
where Self is ImplicitAs(.Self) and
U is ImplicitAs(.Self);
}
match_first {
impl [T:! Type, U:! CommonTypeWith(T)] T as SymmetricCommonTypeWith(U) {
let Result:! Type = U.Result;
}
impl [U:! Type, T:! CommonTypeWith(U)] T as SymmetricCommonTypeWith(U) {
let Result:! Type = T.Result;
}
}
The SymmetricCommonTypeWith interface is not exported, so user-defined impls
can't be defined, and only the two blanket impls above are used. The
CommonType constraint is then defined as follows:
constraint CommonType(U:! SymmetricCommonTypeWith(Self)) {
extend SymmetricCommonTypeWith(U) where .Result == U.Result;
}
When computing the common type of T and U, if only one of the types provides
a CommonTypeWith implementation, that determines the common type. If both
types provide a CommonTypeWith implementation and their Result types are the
same, that determines the common type. Otherwise, if both types provide
implementations but their Result types differ, there is no common type, and
the CommonType constraint is not met. For example, given:
// Implementation #1
impl [T:! Type] MyX as CommonTypeWith(T) {
let Result:! Type = MyX;
}
// Implementation #2
impl [T:! Type] MyY as CommonTypeWith(T) {
let Result:! Type = MyY;
}
MyX as CommonTypeWith(MyY) will select #1, and MyY as CommonTypeWith(MyX)
will select #2, but the constraints on MyX as CommonType(MyY) will not be met
because result types differ.
If T is the same type as U, the result is that type:
final impl [T:! Type] T as CommonTypeWith(T) {
let Result:! Type = T;
}
Note: This rule is intended to be considered more specialized than the other rules in this document.
Because this impl is declared final, T.(CommonType(T)).Result is always
assumed to be T, even in contexts where T involves a generic parameter and
so the result would normally be an unknown type whose type-of-type is Type.
fn F[T:! Hashable](c: bool, x: T, y: T) -> HashCode {
// OK, type of `if` expression is `T`.
return (if c then x else y).Hash();
}
If T implicitly converts to U, the common type is U:
impl [T:! Type, U:! ImplicitAs(T)] T as CommonTypeWith(U) {
let Result:! Type = T;
}
Note: If an implicit conversion is possible in both directions, and no more
specific implementation exists, the constraints on T as CommonType(U) will not
be met because (T as CommonTypeWith(U)).Result and
(U as CommonTypeWith(T)).Result will differ. In order to define a common type
for such a case, CommonTypeWith implementations in both directions must be
provided to override the blanket impls in both directions:
impl MyString as CommonTypeWith(YourString) {
let Result:! Type = MyString;
}
impl YourString as CommonTypeWith(MyString) {
let Result:! Type = MyString;
}
var my_string: MyString;
var your_string: YourString;
// The type of `also_my_string` is `MyString`.
var also_my_string: auto = if cond then my_string else your_string;
cond ? expr1 : expr2, like in C and C++
syntaxif (cond) expr1 else expr2 syntaxif (cond) then expr1 else expr2
syntax1 + if cond then expr1 else expr2impl to specify the common type if implicit conversions in both directions are possible