Short-Circuit Evaluation
Short-circuit evaluation (also called minimal evaluation or lazy boolean evaluation) is the rule by which the binary logical operators && (AND) and || (OR) evaluate their right-hand operand only when the left-hand operand is not sufficient to determine the final result.
How && and || Short-Circuit
In left && right and left || right, Rust always evaluates left first:
- For
left && right:- If
leftisfalse, the entire AND expression must befalse. Rust skips evaluatingrightand immediately returnsfalse. - If
leftistrue, Rust evaluatesrightand returns its result.
- If
- For
left || right:- If
leftistrue, the entire OR expression must betrue. Rust skips evaluatingrightand immediately returnstrue. - If
leftisfalse, Rust evaluatesrightand returns its result.
- If
Short-Circuit Flow for `left && right`:
+---------------+ false
| Evaluate left +------------------------> Returns `false` (`right` NEVER runs!)
+-------+-------+
| true
v
+----------------+
| Evaluate right +-----------------------> Returns value of `right`
+----------------+
Short-Circuit Flow for `left || right`:
+---------------+ true
| Evaluate left +------------------------> Returns `true` (`right` NEVER runs!)
+-------+-------+
| false
v
+----------------+
| Evaluate right +-----------------------> Returns value of `right`
+----------------+
fn check(label: &str, value: bool) -> bool {
println!(" [check] {label} -> {value}");
value
}
fn main() {
println!("1. false && true:");
let r1 = check("left", false) && check("right", true);
println!(" Result = {r1}\n");
println!("2. true && false:");
let r2 = check("left", true) && check("right", false);
println!(" Result = {r2}\n");
println!("3. true || false:");
let r3 = check("left", true) || check("right", false);
println!(" Result = {r3}\n");
println!("4. false || true:");
let r4 = check("left", false) || check("right", true);
println!(" Result = {r4}");
}
1. false && true:
[check] left -> false
Result = false
2. true && false:
[check] left -> true
[check] right -> false
Result = false
3. true || false:
[check] left -> true
Result = true
4. false || true:
[check] left -> false
[check] right -> true
Result = true
Guarding Potentially Panicking Operations
The most important practical use of short-circuit evaluation is writing inline guards where the left-hand condition verifies that the right-hand operation is safe to execute (preventing division by zero or out-of-bounds array indexing):
| Unsafe Operation | Guarded Short-Circuit Idiom | Why It Is Safe |
|---|---|---|
Division total / count |
count != 0 && total / count > 10 |
If count == 0, left is false and total / count is never evaluated |
Slice index slice[i] |
i < slice.len() && slice[i] == target |
If i >= slice.len(), left is false and slice[i] is never indexed |
| Empty slice check | slice.is_empty() || slice[0] == 0 |
If slice.is_empty() is true, left is true and slice[0] is never indexed |
fn is_average_above(total: i32, count: i32, threshold: i32) -> bool {
// Safe: when `count == 0`, the right-hand division `total / count` is skipped!
count > 0 && (total / count) > threshold
}
fn starts_with_magic_byte(data: &[u8]) -> bool {
// Safe: when `data` is empty, `data.len() >= 1` is `false` and `data[0]` is skipped!
data.len() >= 1 && data[0] == 0x7F
}
fn main() {
println!("avg(100, 0, 10): {}", is_average_above(100, 0, 10));
println!("avg(100, 4, 10): {}", is_average_above(100, 4, 10));
println!("magic(&[]): {}", starts_with_magic_byte(&[]));
println!("magic(&[0x7F]): {}", starts_with_magic_byte(&[0x7F]));
}
avg(100, 0, 10): false
avg(100, 4, 10): true
magic(&[]): false
magic(&[0x7F]): true
Warning: Order matters! Writing
data[0] == 0x7F && !data.is_empty()in the reverse order evaluatesdata[0]first and panics with an out-of-bounds error whendatais empty. Always place the bounds or divisor check on the left side of&&or||.
Ordering Conditions for Performance
When combining a cheap in-memory check with an expensive check (such as a regex match, cryptographic hash verification, or database lookup), always place the cheapest and most selective check on the left:
fn cheap_prefix_check(s: &str) -> bool {
println!(" Running fast O(1) prefix check...");
s.starts_with("rust_")
}
fn expensive_deep_validation(s: &str) -> bool {
println!(" Running expensive validation on '{s}'...");
s.chars().all(|c| c.is_ascii_lowercase() || c == '_')
}
fn main() {
let invalid_prefix = "python_script";
let valid_candidate = "rust_compiler";
println!("Checking '{invalid_prefix}':");
let ok1 = cheap_prefix_check(invalid_prefix) && expensive_deep_validation(invalid_prefix);
println!(" Result: {ok1}\n");
println!("Checking '{valid_candidate}':");
let ok2 = cheap_prefix_check(valid_candidate) && expensive_deep_validation(valid_candidate);
println!(" Result: {ok2}");
}
Checking 'python_script':
Running fast O(1) prefix check...
Result: false
Checking 'rust_compiler':
Running fast O(1) prefix check...
Running expensive validation on 'rust_compiler'...
Result: true
Side Effects in the Right-Hand Operand
Because the right-hand operand of && or || is conditionally skipped, avoid placing state-mutating expressions (such as incrementing a counter or writing to a log that must always happen) inside the right-hand operand of && or ||.
If both boolean expressions have side effects that must always run regardless of the left-hand outcome, either:
- Bind both results to local
letvariables before combining them with&&or||, or - Use the non-short-circuiting (eager) boolean operators
&and|.
| Operator | Type | Evaluates Right Operand When Left Decided Result? |
|---|---|---|
a && b |
Short-circuiting AND | No (skips b if a == false) |
a || b |
Short-circuiting OR | No (skips b if a == true) |
a & b |
Eager (non-short-circuiting) AND | Yes (always evaluates both a and b) |
a | b |
Eager (non-short-circuiting) OR | Yes (always evaluates both a and b) |
Diverging Expressions on the Right-Hand Side
Because the right-hand side of || only executes when the left-hand condition is false (and the right-hand side of && only executes when the left-hand condition is true), Rust allows diverging expressions of the never type ! (such as panic!, return, break, or continue) on the right-hand side of && and ||, though explicit if statements are more idiomatic for control flow.