If Statements
if selects control flow using a boolean condition.
It is Sec's direct conditional statement for choosing whether one of
several blocks should execute.
if ready {
Start()
} else {
Wait()
}
Conditions are always explicit boolean expressions. if does not
introduce truthiness, hidden conversions, declaration syntax, assignment
expressions or general pattern destructuring.
if condition has type bool.
Sec never interprets an unrelated value as true or false merely because it appears
in a conditional position.
Syntax
The simplest form is:
if condition {
statements
}
An alternative branch uses else:
if condition {
statements
} else {
statements
}
Several conditions may be chained with else if:
if firstCondition {
...
} else if secondCondition {
...
} else if thirdCondition {
...
} else {
...
}
Braces are mandatory for every branch, including branches containing one statement.
if ready {
Start()
}
This form is invalid:
if ready
Start()
Parentheses
Outer parentheses around a condition are permitted:
if (ready) {
Start()
}
They are normally unnecessary. The formatter removes redundant outer parentheses while preserving parentheses required to express grouping.
if connected && (isAdmin || isOwner) {
AllowEdit()
}
Canonical chain keyword
Sec uses else if. There are no separate
elseif or elif keywords.
Boolean Conditions
The condition expression must produce bool.
let ready: bool := IsReady()
if ready {
Start()
}
A call returning bool may be used directly:
if IsReady() {
Start()
}
Comparison, equality, membership and state-test expressions may also produce boolean values:
if count > 0 {
Process()
}
if role == Role.Admin {
Allow()
}
if value in 0..<100 {
Process(value)
}
Appearing inside an if does not change the ordinary type rules
of the expression.
No Implicit Truthiness
Sec has no general truthy or falsy values.
These are not valid conditions merely because they are non-zero, non-empty or present:
- integers and other numeric values;
- strings;
- collections;
- enums;
- structs;
Option[T];Result[T, E];- safe references;
RawPtr[T].
Invalid:
let count := 3
if count {
Process()
}
Write the actual condition:
if count > 0 {
Process()
}
Similarly, an empty string is not implicitly false:
if !name.IsEmpty {
PrintName(name)
}
Explicit boolean intent avoids assigning unrelated boolean meanings to otherwise well-typed values.
Logical Operators
Boolean expressions use:
!condition
left && right
left || right
! requires one bool operand:
if !finished {
ContinueWork()
}
Both operands of && and || must have type
bool:
if connected && authenticated {
SendPrivateData()
}
if isAdmin || isOwner {
AllowEdit()
}
Relevant precedence, from tighter to looser, is:
!
comparison / equality / membership
&&
||
Parentheses may make grouping explicit:
if connected && (isAdmin || isOwner) {
AllowEdit()
}
Short-Circuit Evaluation
&& and || use guaranteed left-to-right
short-circuit evaluation.
For:
left && right
right is evaluated only when left evaluates to
true.
For:
left || right
right is evaluated only when left evaluates to
false.
This is language semantics, not merely an optimization. Code may rely on short-circuiting for safety:
if index < items.Len && items[index].Ready {
Process(items[index])
}
When index >= items.Len, the indexed access is not evaluated.
Condition Evaluation Order
A condition is evaluated exactly once whenever execution reaches its
if statement.
Normal Sec expression evaluation order applies inside the condition.
For an if / else if chain:
- conditions are evaluated in source order;
- a later condition is evaluated only when every earlier condition was false;
- the first true condition selects its branch;
- later conditions are skipped after a branch has been selected;
elseexecutes only when every preceding condition was false.
if IsPrimaryAvailable() {
UsePrimary()
} else if IsSecondaryAvailable() {
UseSecondary()
} else {
ReportUnavailable()
}
If IsPrimaryAvailable() returns true,
IsSecondaryAvailable() is not called.
Comparison and Equality
Normal Sec comparison and equality rules apply inside conditions.
if value == expected {
...
}
if temperature < maximum {
...
}
An if condition does not create implicit numeric, named-type,
unit or enum conversions.
type Speed int
type Money int
let speed: Speed := 50
let money: Money := 50
if speed == money {
...
}
This is invalid because Speed and Money are
different named types.
No comparison chaining
Comparison chains are not Sec syntax:
if 0 <= value < 100 {
...
}
Use logical composition:
if value >= 0 && value < 100 {
...
}
or, when it expresses the intent more directly, membership:
if value in 0..<100 {
...
}
Membership
A membership expression may be used as an if condition when
the membership operation is valid and produces bool.
if value in minimum..maximum {
Process(value)
}
if key in values {
Use(key)
}
Range and collection membership follow their own type and compatibility rules.
Iterability and membership are separate concepts. A type being iterable does
not automatically make value in container a valid operation.
Calls, Fields and Properties
Any legal expression whose result type is bool may be used
as a condition.
if IsReady() {
...
}
if connection.Ready {
...
}
if !items.IsEmpty {
...
}
A non-boolean property or field must participate in an operation that
produces bool:
if items.Len > 0 {
...
}
Calls and property accesses retain their ordinary effect, ownership, borrowing and fallibility rules when used in a condition.
Option and Result
Option[T] and Result[T, E] are not boolean types.
Invalid:
if optionalValue {
...
}
if result {
...
}
Use the explicit state tests, operations, match or Error Handling
constructs defined for those types.
An if statement does not automatically unwrap an
Option or a Result.
Try in Conditions
A try expression may appear inside a condition when its
successful value has type bool.
if try HasPermission(user, resource) {
Allow()
}
Normal try propagation and local-handling rules still apply.
The successful value must actually be boolean.
if try LoadConfiguration() {
...
}
This is invalid when LoadConfiguration() succeeds with a
Configuration value.
try may remove fallibility from the
condition expression; it does not relax the requirement that the resulting
condition be bool.
State Tests with is
if may use an is expression when the owning
type rule defines that state test as boolean.
if value is Active {
...
}
if value is empty {
...
}
if itself does not invent states or define what an
is test means. Those semantics belong to the relevant type
or feature rule.
Foreign nullable raw pointers have a deliberately narrow form inside the required unsafe FFI context:
unsafe {
if raw is null {
HandleMissingForeignPointer()
}
}
A raw pointer is still not implicitly boolean, and if does not
create an unsafe context.
Option Presence Binding
Sec 0.1 has one narrow payload-binding form for if:
positive Option presence testing.
if option is Some(value) {
Use(value)
}
The binding exists only on the positive true path.
Its ownership behavior follows the ordinary Option, copy,
move and branch-merging rules.
Non-binding state tests remain valid:
if option is None {
HandleMissing()
}
if option is not None {
Continue()
}
Negative payload binding is invalid:
if option is not Some(value) {
Use(value)
}
On the true path, there is no Some payload that could define
value.
No General Pattern Binding in If
The Option.Some(binding) presence form is a narrow exception.
It does not turn if into a general destructuring construct.
Invalid:
if Some(user) := optionalUser {
...
}
Invalid:
if result is Ok(value) {
...
}
Use match when a Result, union or other structural
value must be destructured.
match result {
Ok(value) => Use(value)
Err(error) => Handle(error)
}
if chooses control flow from a boolean
condition. match owns general structural and payload pattern matching.
No Declarations or Assignment in the Header
Sec 0.1 does not place declarations or initializer clauses inside an
if header.
Invalid:
if let user := FindUser(id); user.Active {
...
}
Invalid:
if user := FindUser(id) {
...
}
Declare the value first:
let user := FindUser(id)
if user.Active {
...
}
Assignment is not an expression
Assignment cannot be used as a condition:
if ready = true {
...
}
= is an assignment statement, not a boolean expression.
Equality remains legal:
if ready == true {
...
}
but the direct boolean form is normally clearer:
if ready {
...
}
Else and Else If
else
An else branch executes only when every preceding condition
in its chain is false.
if temperature < FreezingPoint {
StartHeater()
} else {
StopHeater()
}
else if
else if tests additional conditions in source order:
if score >= 90 {
grade = Grade.A
} else if score >= 80 {
grade = Grade.B
} else if score >= 70 {
grade = Grade.C
} else {
grade = Grade.F
}
Exactly one branch of this chain executes.
A final else is optional. Without one, execution simply
continues after the statement when no condition is true.
Nested if statements
if connected {
if authenticated {
SendPrivateData()
} else {
RequestAuthentication()
}
}
Because braces are mandatory, nested if statements do not
have an ambiguous dangling-else form.
Branch Scope
Every branch body creates its own lexical block scope.
if hasInput {
let value := ReadInput()
Process(value)
}
// value is not visible here
A declaration in one branch is not visible in another branch:
if usePrimary {
let connection := OpenPrimary()
Use(connection)
} else {
let connection := OpenSecondary()
Use(connection)
}
These are separate local bindings in separate branch scopes.
Outer bindings remain visible inside branches according to ordinary scope rules. Mutation still requires the appropriate mutability and borrow authority.
Flow Analysis
Branches participate in Sec's ordinary path-sensitive flow analysis. The compiler tracks which paths continue and which paths terminate.
Returning from a branch
fn Normalize(value: int) int {
if value < 0 {
return -value
}
return value
}
The true branch terminates by returning. The false path continues to the final return statement.
Required return analysis
A non-void function must return on every reachable path.
fn Pick(value: int) int {
if value >= 10 {
return 5
} else {
return 6
}
}
Both branches return, so the complete if statement terminates
every reachable path through the function.
Continuing-path merge
After an if, definite-assignment, ownership, borrowing and
availability states are merged across the branches that can actually continue.
A branch that always returns, propagates an error or otherwise terminates does not contribute a continuing state after the statement.
Without an else, the false path remains possible unless the
compiler can prove otherwise.
Unreachable code
fn Pick(value: int) int {
if value >= 10 {
return 5
Log("unreachable")
}
return 6
}
Log(...) is unreachable on that path and is diagnosed by
the general unreachable-code policy.
When every branch of a complete chain terminates, code following the chain is likewise unreachable.
Constant Conditions
Compile-time boolean constants are valid conditions:
if true {
EnableFeature()
}
if false {
}
Constant evaluation does not change the requirement that the condition
have type bool.
When a compile-time condition proves statements unreachable, the ordinary unreachable-code rules apply. An empty unreachable branch is not itself an error merely because the condition is constant.
Optimization may remove unreachable branches after semantic analysis.
Side Effects in Conditions
A condition may contain side effects when those operations are otherwise legal Sec expressions.
if cached || LoadCache() {
UseCache()
}
LoadCache() is called only when cached is false,
because || short-circuits.
Whether side effects inside a condition are good style is a tooling and
readability question, not a special semantic prohibition of if.
If Is a Statement
if controls which statements execute. It does not itself
produce a value.
This is not Sec expression syntax:
let label := if value < 0 {
"negative"
} else {
"positive"
}
Use ordinary control flow when branches update later state:
let mut label := "zero"
if value < 0 {
label = "negative"
} else if value > 0 {
label = "positive"
}
When the decision naturally computes a value, an explicit function or a
value-producing match may be more appropriate.
Match as an If Condition
A value-producing match expression may itself be used as an
if condition when the complete expression has type bool.
if match state {
State.Ready => true
State.Waiting => false
State.Failed => false
} {
Start()
}
The match expression still follows its own exhaustiveness and
arm-typing rules.
A statement-only match does not produce a boolean value and
therefore cannot serve as an if condition.
Formatting
Canonical formatting keeps the complete chain visually connected:
if condition {
...
} else if otherCondition {
...
} else {
...
}
The formatter:
- keeps braces around every branch;
- places
elseon the same line as the preceding closing brace; - removes redundant outer condition parentheses;
- preserves parentheses required for grouping.
The formatter does not rewrite one valid control-flow strategy into a semantically different one.
Best Practices
Prefer direct boolean expressions
if ready {
Start()
}
rather than:
if ready == true {
Start()
}
Prefer membership when it states the intent directly
if value in 0..<100 {
Process(value)
}
rather than:
if value >= 0 && value < 100 {
Process(value)
}
Use match for destructuring
Use match when variant payloads or structural patterns must be
bound. Do not turn an if condition into a second pattern language.
Use short-circuiting deliberately
Put prerequisite safety or state checks before operations that depend on them:
if index < items.Len && items[index].Ready {
Process(items[index])
}
Prefer early returns when they simplify the normal path
fn Process(value: int) Result[void, ProcessError] {
if value < 0 {
return Err(ProcessError.InvalidValue)
}
DoWork(value)
return Ok()
}
Summary
ifselects control flow from a boolean condition.- Every branch body uses braces.
- Outer condition parentheses are permitted but normally removed by the formatter.
- The chained form is
else if; Sec has noelseiforelif. - Every condition must have type
bool. - Sec has no implicit truthiness.
!,&&and||operate on boolean values.&&and||guarantee left-to-right short-circuit evaluation.- An
ifcondition is evaluated exactly once when execution reaches it. else ifconditions are evaluated in source order until one matches.- Normal comparison, equality and membership rules remain in force.
- Comparison chaining is not supported.
OptionandResultare not implicitly boolean.trymay appear in a condition only when its successful value isbool.- Boolean
isstate tests may be used where another rule defines them. - Positive
Optiontesting supports the narrowis Some(binding)form. - General payload and structural pattern binding belongs to
match. - Declarations are not part of Sec 0.1
ifheaders. - Assignment is a statement and cannot be used as a condition.
- Each branch has its own lexical scope.
- Flow analysis merges assignment, ownership and borrow state across continuing branches.
- Branches that terminate do not contribute a continuation state.
- Constant boolean conditions are valid and participate in unreachable-code analysis.
- Legal side effects may appear in conditions and still obey evaluation-order guarantees.
ifis a statement and does not itself produce a value.- A value-producing
matchmay be anifcondition when its type isbool.