Functions and Control Flow
Rust and Swift share a common ancestor in their approach to control flow: both borrow heavily from the ML family of languages, with pattern matching, expression-based constructs, and an emphasis on exhaustiveness. But Rust takes the “everything is an expression” philosophy further than Swift does.
Function syntax
Section titled “Function syntax”The structure of a function declaration is similar in both languages. Swift uses func; Rust uses fn. Both place parameters in parentheses and use -> to denote the return type.
// Swiftfunc add(a: Int, b: Int) -> Int { return a + b}// Rustfn add(a: i32, b: i32) -> i32 { a + b}
fn main() { let result = add(3, 4); println!("{result}");}Several differences are visible immediately:
- No argument labels: Rust does not have Swift’s argument label system. In Swift, you call
add(a: 5, b: 3). In Rust, you calladd(5, 3). There is no concept of external parameter names. - No
returnneeded for the last expression: The last expression in a Rust function body is its return value, without areturnkeyword and without a semicolon. Adding a semicolon turns the expression into a statement that returns(). - Type annotations are required on parameters: Unlike
letbindings where types can be inferred, Rust always requires explicit types on function parameters and return types.
Functions with no return value
Section titled “Functions with no return value”When a function does not return a meaningful value, Swift uses Void (or omits the return type), and Rust implicitly returns ():
// Swiftfunc greet(name: String) { print("Hello, \(name)!")}// Rustfn greet(name: &str) { println!("Hello, {name}!");}
fn main() { greet("Alice");}In both cases, omitting the return type means the function returns the unit type.
Expression-based language
Section titled “Expression-based language”Rust is an expression-based language, meaning that most constructs produce a value. In Swift, if/else became usable as expressions for assignments in Swift 5.9, but Rust has been expression-based from the start. This distinction affects how you write everything from simple conditionals to complex blocks.
Blocks as expressions
Section titled “Blocks as expressions”In Rust, a block { ... } evaluates to the value of its last expression (without a semicolon). This means you can use a block anywhere you need a value:
// Rustfn main() { let description = { let x = 5; let y = 10; if x + y > 10 { "big" } else { "small" } }; println!("{description}");}In Swift, you would typically compute this with a separate variable or an immediately-invoked closure:
// Swiftlet description: String = { let x = 5 let y = 10 if x + y > 10 { return "big" } else { return "small" }}()Or, since Swift 5.9, using if/else as an expression:
// Swift (5.9+)let x = 5let y = 10let description = if x + y > 10 { "big" } else { "small" }Conditionals
Section titled “Conditionals”if / else
Section titled “if / else”The basic if/else syntax is similar. Rust conditions do not use parentheses – they are optional and conventionally omitted. The braces, however, are always required (even for single-line bodies):
// Swiftlet temperature = 35if temperature > 30 { print("Hot")} else if temperature > 20 { print("Warm")} else { print("Cool")}// Rustfn main() { let temperature = 35; if temperature > 30 { println!("Hot"); } else if temperature > 20 { println!("Warm"); } else { println!("Cool"); }}if as an expression
Section titled “if as an expression”In Rust, if/else is always an expression and can be used to produce a value. Both branches must return the same type:
// Rustfn main() { let temperature = 35; let label = if temperature > 30 { "hot" } else { "not hot" }; println!("{label}");}Swift added this capability in 5.9, but it is limited to certain contexts such as assignments, return statements, and variable declarations. In Rust, you can use if expressions anywhere – as function arguments, inside other expressions, and in return position.
No ternary operator
Section titled “No ternary operator”Rust does not have a ternary operator (? :). Since if/else is already an expression, the ternary operator would be redundant:
// Swiftlet label = temperature > 30 ? "hot" : "cool"// Rustfn main() { let temperature = 35; let label = if temperature > 30 { "hot" } else { "cool" }; println!("{label}");}Conditions must be bool
Section titled “Conditions must be bool”Unlike C, but like Swift, Rust requires conditions to be explicitly bool. You cannot use an integer or pointer as a condition:
// Rust – this does not compile// fn main() {// let count = 1;// if count { // error: expected `bool`, found integer// println!("truthy");// }// }Rust has three loop constructs: loop, while, and for. Swift has while, for-in, and repeat-while. The mapping is not one-to-one.
loop: infinite loops
Section titled “loop: infinite loops”Rust has a dedicated loop keyword for infinite loops. Swift uses while true for the same purpose:
// Swiftwhile true { // runs forever break}// Rustfn main() { loop { // runs forever break; }}Using loop instead of while true is not just convention – it gives the compiler useful information. The compiler knows a loop never terminates naturally, which enables better type checking and control-flow analysis.
loop as an expression
Section titled “loop as an expression”Since Rust is expression-based, loop can return a value via break:
// Rustfn main() { let mut counter = 0; let result = loop { counter += 1; if counter == 10 { break counter * 2; } }; println!("{result}"); // 20}Swift has no equivalent – you cannot return a value from a while or repeat-while loop.
while loops
Section titled “while loops”while loops work the same way in both languages:
// Swiftvar n = 5while n > 0 { print(n) n -= 1}// Rustfn main() { let mut n = 5; while n > 0 { println!("{n}"); n -= 1; }}Swift’s repeat-while (do-while in other languages) does not have a direct Rust equivalent. You can achieve the same behavior with loop and a conditional break at the end of the body.
for loops and ranges
Section titled “for loops and ranges”Both languages use for-in syntax for iterating over sequences, but the range syntax differs:
// Swiftfor i in 0..<5 { print(i) // 0, 1, 2, 3, 4}
for i in 0...5 { print(i) // 0, 1, 2, 3, 4, 5}// Rustfn main() { for i in 0..5 { println!("{i}"); // 0, 1, 2, 3, 4 }
for i in 0..=5 { println!("{i}"); // 0, 1, 2, 3, 4, 5 }}| Swift | Rust | Meaning |
|---|---|---|
0..<5 | 0..5 | Half-open (excludes end) |
0...5 | 0..=5 | Closed (includes end) |
Rust’s for loop works with anything that implements the IntoIterator trait – arrays, vectors, ranges, and custom types. This is similar to how Swift’s for-in works with anything conforming to Sequence.
// Rustfn main() { let names = ["Alice", "Bob", "Charlie"]; for name in names { println!("Hello, {name}!"); }}Loop labels
Section titled “Loop labels”Rust supports labeling loops, which is useful for breaking out of nested loops. Swift has a similar feature:
// Swiftouter: for i in 0..<5 { for j in 0..<5 { if i + j == 6 { break outer } }}// Rustfn main() { 'outer: for i in 0..5 { for j in 0..5 { if i + j == 6 { break 'outer; } } }}Rust labels start with a single quote ('outer), while Swift labels use a bare identifier followed by a colon (outer:).
Early returns
Section titled “Early returns”Both languages use return for early exits from functions:
// Rustfn classify(n: i32) -> &'static str { if n < 0 { return "negative"; } if n == 0 { return "zero"; } "positive" // last expression, no return needed}
fn main() { println!("{}", classify(-5)); println!("{}", classify(0)); println!("{}", classify(7));}The convention in Rust is to use return only for early exits. When the last expression in a function is the return value, you omit both return and the semicolon. Adding an explicit return and semicolon on the last expression works but is not idiomatic.
The semicolon trap
Section titled “The semicolon trap”One of the most common mistakes for newcomers is accidentally adding a semicolon to the last expression:
// Rust – this does not compile// fn double(x: i32) -> i32 {// x * 2; // semicolon turns this into a statement returning ()// }The semicolon converts the expression x * 2 into a statement, so the function body returns () instead of i32. The compiler will report a type mismatch. Remove the semicolon to fix it:
// Rustfn double(x: i32) -> i32 { x * 2}
fn main() { println!("{}", double(5));}Diverging functions
Section titled “Diverging functions”Rust has a special return type ! (pronounced “never”) for functions that never return. These are called diverging functions:
// Rustfn forever() -> ! { loop { // this function never returns }}Common uses include functions that always panic, run an infinite event loop, or call std::process::exit. The ! type is useful because it can coerce to any other type, which allows you to use a diverging call in contexts that expect a specific type:
// Rustfn get_value(opt: Option<i32>) -> i32 { match opt { Some(v) => v, None => panic!("no value"), // panic! returns !, which coerces to i32 }}
fn main() { println!("{}", get_value(Some(42)));}Swift has a similar concept with the Never type (used as the return type of fatalError, preconditionFailure, and similar functions), but you rarely write -> Never in your own Swift code. In Rust, -> ! appears more frequently because the type system uses it for type coercion in match arms and other contexts.
Key differences and gotchas
Section titled “Key differences and gotchas”- No argument labels: Rust functions do not have external parameter names. All calls are positional.
- Semicolons matter: Omitting the semicolon on the last expression makes it the return value. Adding a semicolon makes it a statement that returns
(). - Braces are always required: Even single-line
ifbodies need braces in Rust. - No ternary operator: Use
if/elseas an expression instead. loopvswhile true: Preferloopfor infinite loops in Rust – it is not just convention, it helps the compiler.- Range syntax differs:
0..<5in Swift is0..5in Rust.0...5in Swift is0..=5in Rust. - Loop labels: Rust uses
'label(with a single quote prefix); Swift useslabel:. returnconvention: Usereturnonly for early exits. The last expression in a block is its value.- No
repeat-while: Rust does not have a do-while construct. Useloopwith a conditionalbreak.
Further reading
Section titled “Further reading”- Functions: The Rust Programming Language
- Control Flow: The Rust Programming Language
- Expressions: The Rust Reference
- The never type: Rust standard library documentation