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.

Rule: Every 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.

Rule: Short-circuit operators may guard later operations whose safety or meaning depends on an earlier condition.

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:

  1. conditions are evaluated in source order;
  2. a later condition is evaluated only when every earlier condition was false;
  3. the first true condition selects its branch;
  4. later conditions are skipped after a branch has been selected;
  5. else executes 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.

Rule: 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)
}
Boundary: 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 else on 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

  • if selects 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 no elseif or elif.
  • 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 if condition is evaluated exactly once when execution reaches it.
  • else if conditions are evaluated in source order until one matches.
  • Normal comparison, equality and membership rules remain in force.
  • Comparison chaining is not supported.
  • Option and Result are not implicitly boolean.
  • try may appear in a condition only when its successful value is bool.
  • Boolean is state tests may be used where another rule defines them.
  • Positive Option testing supports the narrow is Some(binding) form.
  • General payload and structural pattern binding belongs to match.
  • Declarations are not part of Sec 0.1 if headers.
  • 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.
  • if is a statement and does not itself produce a value.
  • A value-producing match may be an if condition when its type is bool.