If Statements
An if statement conditionally executes a block of code. The condition is evaluated
once. If it evaluates to true, the first branch is executed. If it evaluates to
false, execution continues with an optional else if or else
branch.
Sec requires conditions to have type bool. Numbers, strings, references and other
values are never interpreted as true or false implicitly.
Basic Syntax
The simplest form contains one condition and one block:
if condition {
statements
}
Example:
if rawResult < 0 {
return Err(_decodeError(-rawResult))
}
Parentheses are not required around the condition. Braces are required around the branch body, including when the body contains only one statement.
if ready {
Start()
}
The following form is not valid Sec syntax:
if (ready)
Start()
if condition is written without mandatory parentheses,
and every branch body is enclosed by braces.
Conditions
The expression following if must produce a bool. A boolean variable may
be used directly:
let connected := IsConnected()
if connected {
SendData()
}
A function call returning bool may also be used directly:
if IsConnected() {
SendData()
}
More commonly, the condition is produced by a comparison or a logical expression.
No Implicit Truthiness
Sec has no truthy or falsy values. An integer is not false when it is zero, a string is not false when it is empty, and a reference is not true merely because it exists.
let count := 3
if count {
Process()
} // Error: if condition must be bool, got int.
The intended test must be written explicitly:
if count > 0 {
Process()
}
Similarly, strings must be compared or inspected explicitly:
let name := "Anna"
if name != "" {
PrintName(name)
}
Explicit conditions make the programmer's intent visible and prevent values from acquiring unrelated boolean meanings.
Comparison Conditions
Comparison operators produce bool values and may therefore be used as conditions:
==
!=
<
<=
>
>=
Example:
if rawResult < 0 {
return Err(_decodeError(-rawResult))
}
The operands must support the selected comparison. Named types retain their distinct type identity, so unrelated named types cannot be compared merely because they share the same underlying representation.
type Speed int
type Money int
let speed: Speed := 50
let money: Money := 50
if speed == money {
} // Error: Speed and Money are different types.
Logical Conditions
Boolean expressions may be combined with the logical operators:
!condition
left && right
left || right
Both operands of && and || must be bool.
if connected && authenticated {
SendPrivateData()
}
if isAdmin || isOwner {
AllowEdit()
}
if !finished {
ContinueWork()
}
Logical expressions may be grouped with parentheses when grouping is needed for clarity or precedence:
if connected && (isAdmin || isOwner) {
AllowEdit()
}
else
An else branch runs when the preceding if condition is false.
if temperature < FreezingPoint {
StartHeater()
} else {
StopHeater()
}
Exactly one of the two branches is executed. After the selected branch finishes, execution
continues after the complete if statement unless the branch has already returned or
otherwise terminated control flow.
else if Chains
Several conditions may be tested in order by using else if. Conditions are evaluated
from top to bottom. The first condition that evaluates to true selects its branch.
if score >= 90 {
grade = Grade.A
} else if score >= 80 {
grade = Grade.B
} else if score >= 70 {
grade = Grade.C
} else {
grade = Grade.F
}
Once a branch has been selected, later conditions in the chain are not evaluated.
A final else is optional. Without it, execution simply continues after the chain
when no condition matches.
Nested if Statements
An if statement may appear inside any branch:
if connected {
if authenticated {
SendPrivateData()
} else {
RequestAuthentication()
}
}
Nesting is useful when the second condition is meaningful only after the first condition has succeeded. When several conditions belong to one decision, a combined boolean expression may be clearer.
if connected && authenticated {
SendPrivateData()
}
Branch Scope
Every branch body is a block and therefore creates a child scope. Variables declared inside a branch are visible only inside that branch and its nested scopes.
if hasInput {
let value := ReadInput()
Process(value)
}
Process(value) // Error: unknown symbol value.
Separate branches may use the same local variable name because each branch has its own scope:
if usePrimary {
let connection := OpenPrimary()
Use(connection)
} else {
let connection := OpenSecondary()
Use(connection)
}
Variables declared before the if statement remain visible inside its branches.
Mutating such a variable requires that it was declared with let mut.
let mut result := 0
if useAlternative {
result = 10
}
Returning from Branches
A branch may return from the enclosing function. Once return is executed, no later
statement in that function invocation is reached.
fn Normalize(value: int) int {
if value < 0 {
return -value
}
return value
}
This form is often clearer than wrapping the remainder of the function in an
else block. The early-return branch handles the exceptional or special case, while
the normal path continues without additional nesting.
Required Return Analysis
A non-void function must return a value on every reachable path. An
if statement satisfies this requirement only when every possible branch returns.
Valid:
fn Pick(value: int) int {
if value >= 10 {
return 5
} else {
return 6
}
}
Both possible paths return an int, so no additional return statement is required.
Also valid:
fn Pick(value: int) int {
if value >= 10 {
return 5
}
return 6
}
When the condition is false, execution continues to the final return statement.
Invalid:
fn Pick(value: int) int {
if value >= 10 {
return 5
}
} // Error: function Pick must return int.
The condition may be false, leaving a reachable path that does not return a value.
In an else if chain, every branch must return and the chain must end with
else before the chain alone can satisfy the function's required return.
Unreachable Code
Statements following an unconditional return in the same control-flow path are
unreachable and are rejected.
fn Pick(value: int) int {
if value >= 10 {
return 5
Log("unreachable") // Error: unreachable statement.
}
return 6
}
When every branch of an if statement returns, statements following the complete
statement are also unreachable:
fn Pick(value: int) int {
if value >= 10 {
return 5
} else {
return 6
}
return 7 // Error: unreachable statement.
}
An if statement without an else does not by itself make following code
unreachable, because its condition may be false.
if Is a Statement
In the current language model, if controls which statements execute. It does not
itself produce a value.
Use assignment to a mutable variable when a branch must select a later value:
let mut label := "normal"
if value < 0 {
label = "negative"
} else if value > 0 {
label = "positive"
}
Use a function with explicit returns when the decision naturally computes a value:
fn Describe(value: int) string {
if value < 0 {
return "negative"
} else if value > 0 {
return "positive"
}
return "zero"
}
Result-returning Function
The following function combines unsafe system interaction, an if statement and a
typed Result return value:
fn Write(fd: int, data: []byte) Result[uint, LinuxError] {
let rawResult: int
unsafe {
rawResult = _rawSyscall3(
_sysWrite,
uint(fd),
uint(data.ptr),
data.len,
)
}
if rawResult < 0 {
return Err(_decodeError(-rawResult))
}
return Ok(uint(rawResult))
}
The function has two reachable outcomes:
- A negative system-call result is converted into
LinuxErrorand returned asErr(...). - A non-negative result is converted to
uintand returned asOk(...).
The error path returns early. The normal path therefore continues without an
else branch. This keeps the successful path visually direct and avoids unnecessary
nesting.
Common Errors
Using a non-boolean condition
let count := 10
if count {
} // Error: if condition must be bool, got int.
Omitting braces
if ready
Start()
Reading a branch-local variable outside its scope
if ready {
let value := Load()
}
Use(value) // Error: unknown symbol value.
Failing to return from every path
fn Value(valid: bool) int {
if valid {
return 1
}
} // Error: function Value must return int.
Writing code after an unconditional return
if failed {
return Err(error)
Log(error) // Error: unreachable statement.
}
Summary of Rules
- An
ifcondition must have typebool. - Sec does not implicitly convert other values to
bool. - Parentheses around the condition are optional and normally unnecessary.
- Every branch body requires braces.
else ifconditions are evaluated from top to bottom.- Only the first matching branch is executed.
- Every branch creates its own local scope.
- Variables declared inside a branch are not visible outside that branch.
- An
ifstatement may return from the enclosing function. - A non-
voidfunction must return on every reachable path. - An
ifchain guarantees a return only when all possible branches return. - Code after an unconditional return path is unreachable.
ifis currently a control-flow statement and does not produce a value.