If
if has four uses: conditional statement, conditional expression, conditional binding, and type-test entry point. The condition slot accepts only a bool; an integer or any other type is not allowed there.
if, elif, else
func Grade(score: int) -> string
{
if score >= 90
{
return "excellent";
}
elif score >= 75
{
return "good";
}
elif score >= 60
{
return "pass";
}
else
{
return "fail";
}
}
print(Grade(95), Grade(80), Grade(65), Grade(50));excellent good pass fail- Parentheses around the condition are optional;
if (x > 0)andif x > 0are equivalent. - Use
eliffor multi-way branching.else ifdoes not exist; the two-word form raises MS2016. - When the condition is true but the branch is empty, write the body as
{ }.
The if Expression
Used in a value position, if is an expression: both arms must have the same type, and else is mandatory:
val label = if 5 > 3 { "yes" } else { "no" };
print(label);yesThe last expression in a body is the branch’s value, and a single-expression statement may drop the semicolon. An elif chain works as an expression too; the trailing else is still required:
val w = if n < 0 { "negative" } elif n == 0 { "zero" } else { "positive" };if val and if var: Conditional Bindings
if val pattern = expression takes the expression apart by the pattern; on a successful match the block runs with the bindings visible inside it, and on failure control goes to else:
var maybe: int? = 42;
if val n = maybe
{
print("got", n);
}
else
{
print("none");
}
maybe = null;
if val n = maybe
{
print("got", n);
}
else
{
print("none");
}got 42
none- A
valbinding is read-only; avarbinding can be reassigned inside the block.
if var m = 7
{
m = 8;
print("var binding reassigned", m);
}var binding reassigned 8- The pattern slot supports every form on the Pattern page, not just the null check.
- The bound variables live only inside the binding block; the else block cannot see them, because the match already failed.
is Declaration Patterns
x is T p tests a type in the condition slot and binds p to the narrowed value:
val box: any = "some text";
if box is string t
{
print("length", t.Length);
}length 9Inside the block, t is a string, so members are directly accessible. This form only accepts a bare condition or an && chain: the negated form !(x is T p) and a plain bool position (val b = x is T p;) are both rejected.
guard: An if with Only an else Block
An if with an else block and no then block is a guard: the else block must diverge, ending in return / fail / crash (inside loop bodies, break / continue also count). Getting past a guard means the condition held:
func Read(b: Box?) -> int
{
if (b != null) else
{
return -1;
}
return b.v; // b is treated as non-null here; null-check narrowing flows through the guard
}The condition slot can also be a val / var / const binding (the pattern-matching form, taking over the retired expect semantics): when the pattern matches, the bound variables are available after the guard; on failure, control goes to else:
enum Result
{
Ok(value: int),
Bad,
}
error DemoNeedsOk();
func Demo(r: Result) -> int
{
if val Result.Ok(n) = r else
{
crash new DemoNeedsOk();
}
return n * 2;
}Two disciplines for guards:
- else must diverge: “execution reaches here = the condition held” is the basis for narrowing and binding visibility, not syntactic sugar;
- the pattern must be refutable: a binding that can never fail, as in
if val n = 42 else { ... }, raises MS4245 when given an else (drop the else, or switch to a variant/literal pattern).
The division of labor with if: a regular if says both paths are normal; a guard says this path is blocked, so leave. Use guards for precondition checks, argument validation, and unwrap assertions.
Notes
- Null-check narrowing is not limited to if val: after a guard-style check such as
if maybe == null else { return; },maybeis treated as non-null in the code that follows. - Boxed scalars (
int?and friends) are not narrowed by an== nulltest; useif valfor null-check binding. - An if expression is less direct than match for multi-way classification; past three branches, switch to match.
