Statements
In Swift, there are three kinds of statements: simple statements, compiler control statements, and control flow statements. Simple statements are the most common and consist of either an expression or a declaration. Compiler control statements allow the program to change aspects of the compiler’s behavior and include a conditional compilation block and a line control statement.
Control flow statements are used to control the flow of execution in a program. There are several types of control flow statements in Swift, including loop statements, branch statements, and control transfer statements. Loop statements allow a block of code to be executed repeatedly, branch statements allow a certain block of code to be executed only when certain conditions are met, and control transfer statements provide a way to alter the order in which code is executed. In addition, Swift provides a do
statement to introduce scope, and catch and handle errors, and a defer
statement for running cleanup actions just before the current scope exits.
A semicolon (;
) can optionally appear after any statement and is used to separate multiple statements if they appear on the same line.
Loop Statements
Loop statements allow a block of code to be executed repeatedly, depending on the conditions specified in the loop. Swift has three loop statements: a for
-in
statement, a while
statement, and a repeat
-while
statement.
Control flow in a loop statement can be changed by a break
statement and a continue
statement and is discussed in Break Statement and Continue Statement below.
For-In Statement
A for
-in
statement allows a block of code to be executed once for each item in a collection (or any type) that conforms to the Sequence
protocol.
A for
-in
statement has the following form:
The makeIterator()
method is called on the collection expression to obtain a value of an iterator type—that is, a type that conforms to the IteratorProtocol
protocol. The program begins executing a loop by calling the next()
method on the iterator. If the value returned is not nil
, it is assigned to the item pattern, the program executes the statements, and then continues execution at the beginning of the loop. Otherwise, the program does not perform assignment or execute the statements, and it is finished executing the for
-in
statement.
Grammar of a for-in statement
for-in-statement → for
case
opt pattern in
expression where-clause opt code-block
While Statement
A while
statement allows a block of code to be executed repeatedly, as long as a condition remains true.
A while
statement has the following form:
A while
statement is executed as follows:
-
The condition is evaluated.
If
true
, execution continues to step 2. Iffalse
, the program is finished executing thewhile
statement. -
The program executes the statements, and execution returns to step 1.
Because the value of the condition is evaluated before the statements are executed, the statements in a while
statement can be executed zero or more times.
The value of the condition must be of type Bool
or a type bridged to Bool
. The condition can also be an optional binding declaration, as discussed in Optional Binding.
Grammar of a while statement
while-statement → while
condition-list code-block
condition-list → condition | condition ,
condition-list
condition → expression | availability-condition | case-condition | optional-binding-condition
case-condition → case
pattern initializer
optional-binding-condition → let
pattern initializer | var
pattern initializer
Repeat-While Statement
A repeat
-while
statement allows a block of code to be executed one or more times, as long as a condition remains true.
A repeat
-while
statement has the following form:
A repeat
-while
statement is executed as follows:
-
The program executes the statements, and execution continues to step 2.
-
The condition is evaluated.
If
true
, execution returns to step 1. Iffalse
, the program is finished executing therepeat
-while
statement.
Because the value of the condition is evaluated after the statements are executed, the statements in a repeat
-while
statement are executed at least once.
The value of the condition must be of type Bool
or a type bridged to Bool
. The condition can also be an optional binding declaration, as discussed in Optional Binding.
Grammar of a repeat-while statement
repeat-while-statement → repeat
code-block while
expression
Branch Statements
Branch statements allow the program to execute certain parts of code depending on the value of one or more conditions. The values of the conditions specified in a branch statement control how the program branches and, therefore, what block of code is executed. Swift has three branch statements: an if
statement, a guard
statement, and a switch
statement.
Control flow in an if
statement or a switch
statement can be changed by a break
statement and is discussed in Break Statement below.
Grammar of a branch statement
branch-statement → if-statement
branch-statement → guard-statement
branch-statement → switch-statement
If Statement
An if
statement is used for executing code based on the evaluation of one or more conditions.
There are two basic forms of an if
statement. In each form, the opening and closing braces are required.
The first form allows code to be executed only when a condition is true and has the following form:
The second form of an if
statement provides an additional else clause (introduced by the else
keyword) and is used for executing one part of code when the condition is true and another part of code when the same condition is false. When a single else clause is present, an if
statement has the following form:
The else clause of an if
statement can contain another if
statement to test more than one condition. An if
statement chained together in this way has the following form:
The value of any condition in an if
statement must be of type Bool
or a type bridged to Bool
. The condition can also be an optional binding declaration, as discussed in Optional Binding.
Grammar of an if statement
if-statement → if
condition-list code-block else-clause opt
else-clause → else
code-block | else
if-statement
Guard Statement
A guard
statement is used to transfer program control out of a scope if one or more conditions aren’t met.
A guard
statement has the following form:
The value of any condition in a guard
statement must be of type Bool
or a type bridged to Bool
. The condition can also be an optional binding declaration, as discussed in Optional Binding.
Any constants or variables assigned a value from an optional binding declaration in a guard
statement condition can be used for the rest of the guard statement’s enclosing scope.
The else
clause of a guard
statement is required, and must either call a function with the Never
return type or transfer program control outside the guard statement’s enclosing scope using one of the following statements:
-
return
-
break
-
continue
-
throw
Control transfer statements are discussed in Control Transfer Statements below. For more information on functions with the Never
return type, see Functions that Never Return.
Grammar of a guard statement
guard-statement → guard
condition-list else
code-block
Switch Statement
A switch
statement allows certain blocks of code to be executed depending on the value of a control expression.
A switch
statement has the following form:
The control expression of the switch
statement is evaluated and then compared with the patterns specified in each case. If a match is found, the program executes the statements listed within the scope of that case. The scope of each case can’t be empty. As a result, you must include at least one statement following the colon (:
) of each case label. Use a single break
statement if you don’t intend to execute any code in the body of a matched case.
The values of expressions your code can branch on are very flexible. For instance, in addition to the values of scalar types, such as integers and characters, your code can branch on the values of any type, including floating-point numbers, strings, tuples, instances of custom classes, and optionals. The value of the control expression can even be matched to the value of a case in an enumeration and checked for inclusion in a specified range of values. For examples of how to use these various types of values in switch
statements, see Switch in Control Flow.
A switch
case can optionally contain a where
clause after each pattern. A where clause is introduced by the where
keyword followed by an expression, and is used to provide an additional condition before a pattern in a case is considered matched to the control expression. If a where
clause is present, the statements within the relevant case are executed only if the value of the control expression matches one of the patterns of the case and the expression of the where
clause evaluates to true
. For instance, a control expression matches the case in the example below only if it is a tuple that contains two elements of the same value, such as (1, 1)
.
As the above example shows, patterns in a case can also bind constants using the let
keyword (they can also bind variables using the var
keyword). These constants (or variables) can then be referenced in a corresponding where
clause and throughout the rest of the code within the scope of the case. If the case contains multiple patterns that match the control expression, all of the patterns must contain the same constant or variable bindings, and each bound variable or constant must have the same type in all of the case’s patterns.
A switch
statement can also include a default case, introduced by the default
keyword. The code within a default case is executed only if no other cases match the control expression. A switch
statement can include only one default case, which must appear at the end of the switch
statement.
Although the actual execution order of pattern-matching operations, and in particular the evaluation order of patterns in cases, is unspecified, pattern matching in a switch
statement behaves as if the evaluation is performed in source order—that is, the order in which they appear in source code. As a result, if multiple cases contain patterns that evaluate to the same value, and thus can match the value of the control expression, the program executes only the code within the first matching case in source order.
Switch Statements Must Be Exhaustive
In Swift, every possible value of the control expression’s type must match the value of at least one pattern of a case. When this simply isn’t feasible (for instance, when the control expression’s type is Int
), you can include a default case to satisfy the requirement.
Execution Does Not Fall Through Cases Implicitly
After the code within a matched case has finished executing, the program exits from the switch
statement. Program execution does not continue or “fall through” to the next case or default case. That said, if you want execution to continue from one case to the next, explicitly include a fallthrough
statement, which simply consists of the fallthrough
keyword, in the case from which you want execution to continue. For more information about the fallthrough
statement, see Fallthrough Statement below.
Labeled Statement
You can prefix a loop statement, an if
statement, a switch
statement, or a do
statement with a statement label, which consists of the name of the label followed immediately by a colon (:). Use statement labels with break
and continue
statements to be explicit about how you want to change control flow in a loop statement or a switch
statement, as discussed in Break Statement and Continue Statement below.
The scope of a labeled statement is the entire statement following the statement label. You can nest labeled statements, but the name of each statement label must be unique.
For more information and to see examples of how to use statement labels, see Labeled Statements in Control Flow.
Control Transfer Statements
Control transfer statements can change the order in which code in your program is executed by unconditionally transferring program control from one piece of code to another. Swift has five control transfer statements: a break
statement, a continue
statement, a fallthrough
statement, a return
statement, and a throw
statement.
Break Statement
A break
statement ends program execution of a loop, an if
statement, or a switch
statement. A break
statement can consist of only the break
keyword, or it can consist of the break
keyword followed by the name of a statement label, as shown below.
When a break
statement is followed by the name of a statement label, it ends program execution of the loop, if
statement, or switch
statement named by that label.
When a break
statement is not followed by the name of a statement label, it ends program execution of the switch
statement or the innermost enclosing loop statement in which it occurs. You can’t use an unlabeled break
statement to break out of an if
statement.
In both cases, program control is then transferred to the first line of code following the enclosing loop or switch
statement, if any.
For examples of how to use a break
statement, see Break and Labeled Statements in Control Flow.
Continue Statement
A continue
statement ends program execution of the current iteration of a loop statement but does not stop execution of the loop statement. A continue
statement can consist of only the continue
keyword, or it can consist of the continue
keyword followed by the name of a statement label, as shown below.
When a continue
statement is followed by the name of a statement label, it ends program execution of the current iteration of the loop statement named by that label.
When a continue
statement is not followed by the name of a statement label, it ends program execution of the current iteration of the innermost enclosing loop statement in which it occurs.
In both cases, program control is then transferred to the condition of the enclosing loop statement.
In a for
statement, the increment expression is still evaluated after the continue
statement is executed, because the increment expression is evaluated after the execution of the loop’s body.
For examples of how to use a continue
statement, see Continue and Labeled Statements in Control Flow.
Fallthrough Statement
A fallthrough
statement consists of the fallthrough
keyword and occurs only in a case block of a switch
statement. A fallthrough
statement causes program execution to continue from one case in a switch
statement to the next case. Program execution continues to the next case even if the patterns of the case label do not match the value of the switch
statement’s control expression.
A fallthrough
statement can appear anywhere inside a switch
statement, not just as the last statement of a case block, but it can’t be used in the final case block. It also cannot transfer control into a case block whose pattern contains value binding patterns.
For an example of how to use a fallthrough
statement in a switch
statement, see Control Transfer Statements in Control Flow.
Return Statement
A return
statement occurs in the body of a function or method definition and causes program execution to return to the calling function or method. Program execution continues at the point immediately following the function or method call.
A return
statement can consist of only the return
keyword, or it can consist of the return
keyword followed by an expression, as shown below.
When a return
statement is followed by an expression, the value of the expression is returned to the calling function or method. If the value of the expression does not match the value of the return type declared in the function or method declaration, the expression’s value is converted to the return type before it is returned to the calling function or method.
Note
As described in Failable Initializers, a special form of the return
statement (return nil
) can be used in a failable initializer to indicate initialization failure.
When a return
statement is not followed by an expression, it can be used only to return from a function or method that does not return a value (that is, when the return type of the function or method is Void
or ()
).
Throw Statement
A throw
statement occurs in the body of a throwing function or method, or in the body of a closure expression whose type is marked with the throws
keyword.
A throw
statement causes a program to end execution of the current scope and begin error propagation to its enclosing scope. The error that’s thrown continues to propagate until it’s handled by a catch
clause of a do
statement.
A throw
statement consists of the throw
keyword followed by an expression, as shown below.
The value of the expression must have a type that conforms to the Error
protocol.
For an example of how to use a throw
statement, see Propagating Errors Using Throwing Functions in Error Handling.
Defer Statement
A defer
statement is used for executing code just before transferring program control outside of the scope that the defer
statement appears in.
A defer
statement has the following form:
The statements within the defer
statement are executed no matter how program control is transferred. This means that a defer
statement can be used, for example, to perform manual resource management such as closing file descriptors, and to perform actions that need to happen even if an error is thrown.
If multiple defer
statements appear in the same scope, the order they appear is the reverse of the order they are executed. Executing the last defer
statement in a given scope first means that statements inside that last defer
statement can refer to resources that will be cleaned up by other defer
statements.
The statements in the defer
statement can’t transfer program control outside of the defer
statement.
Do Statement
The do
statement is used to introduce a new scope and can optionally contain one or more catch
clauses, which contain patterns that match against defined error conditions. Variables and constants declared in the scope of a do
statement can be accessed only within that scope.
A do
statement in Swift is similar to curly braces ({}
) in C used to delimit a code block, and does not incur a performance cost at runtime.
A do
statement has the following form:
Like a switch
statement, the compiler attempts to infer whether catch
clauses are exhaustive. If such a determination can be made, the error is considered handled. Otherwise, the error can propagate out of the containing scope, which means the error must be handled by an enclosing catch
clause or the containing function must be declared with throws
.
To ensure that an error is handled, use a catch
clause with a pattern that matches all errors, such as a wildcard pattern (_
). If a catch
clause does not specify a pattern, the catch
clause matches and binds any error to a local constant named error
. For more information about the patterns you can use in a catch
clause, see Patterns.
To see an example of how to use a do
statement with several catch
clauses, see Handling Errors.
Compiler Control Statements
Compiler control statements allow the program to change aspects of the compiler’s behavior. Swift has three compiler control statements: a conditional compilation block a line control statement, and a compile-time diagnostic statement.
Conditional Compilation Block
A conditional compilation block allows code to be conditionally compiled depending on the value of one or more compilation conditions.
Every conditional compilation block begins with the #if
compilation directive and ends with the #endif
compilation directive. A simple conditional compilation block has the following form:
Unlike the condition of an if
statement, the compilation condition is evaluated at compile time. As a result, the statements are compiled and executed only if the compilation condition evaluates to true
at compile time.
The compilation condition can include the true
and false
Boolean literals, an identifier used with the -D
command line flag, or any of the platform conditions listed in the table below.
Platform condition | Valid arguments |
---|---|
os() |
macOS , iOS , watchOS , tvOS , Linux |
arch() |
i386 , x86_64 , arm , arm64 |
swift() |
>= followed by a version number |
compiler() |
>= followed by a version number |
canImport() |
A module name |
targetEnvironment() |
simulator |
The version number for the swift()
and compiler()
platform conditions consists of a major number, optional minor number, optional patch number, and so on, with a dot (.
) separating each part of the version number. There must not be whitespace between >=
and the version number. The version for compiler()
is the compiler version, regardless of the Swift version setting passed to the compiler. The version for swift()
is the language version currently being compiled. For example, if you compile your code using the Swift 4.2 compiler in Swift 3 mode, the compiler version is 4.2 and the language version is 3.3. With those settings, the following code prints only the first and last messages:
The argument for the canImport()
platform condition is the name of a module that may not be present on all platforms. This condition tests whether it’s possible to import the module, but doesn’t actually import it. If the module is present, the platform condition returns true
; otherwise, it returns false
.
The targetEnvironment()
platform condition returns true
when code is compiled for a simulator; otherwise, it returns false
.
Note
The arch(arm)
platform condition does not return true
for ARM 64 devices. The arch(i386)
platform condition returns true
when code is compiled for the 32–bit iOS simulator.
You can combine compilation conditions using the logical operators &&
, ||
, and !
and use parentheses for grouping. These operators have the same associativity and precedence as the logical operators that are used to combine ordinary Boolean expressions.
Similar to an if
statement, you can add multiple conditional branches to test for different compilation conditions. You can add any number of additional branches using #elseif
clauses. You can also add a final additional branch using an #else
clause. Conditional compilation blocks that contain multiple branches have the following form:
Note
Each statement in the body of a conditional compilation block is parsed even if it’s not compiled. However, there is an exception if the compilation condition includes a swift()
platform condition: The statements are parsed only if the compiler’s version of Swift matches what is specified in the platform condition. This exception ensures that an older compiler doesn’t attempt to parse syntax introduced in a newer version of Swift.