A Rust crate that provides Python-style for
and while
loops with an optional else
clause.
- Optional Else Clause: For both
for
andwhile
loops, theelse
clause is executed if the loop completes without abreak
statement and without encountering a panic. for
loop features:- Inclusive/Exclusive Ranges: Supports both inclusive (
..=
) and exclusive (..
) ranges. - Step Values: Allows specifying a step value for iteration, including negative steps for reverse iteration.
- Collection Support: Works with various Rust collections (Vec, HashMap, HashSet, etc.) and custom iterators.
- Inclusive/Exclusive Ranges: Supports both inclusive (
while
loop features:- Standard
while
anddo-while
variants: Supports both commonwhile
loop forms.
- Standard
- Error Handling: Consistent panic handling for both loop types (else clause skipped).
- Break Statement Handling:
break
statements withinpythonic_for!
orpythonic_while!
bodies correctly prevent their respectiveelse
clauses from executing.
Add this to your Cargo.toml
:
[dependencies]
pythonic-for = "0.1.0" # Replace with the latest version
use pythonic_for::pythonic_for;
// Simple for loop without else clause
let mut sum = 0;
pythonic_for!(i in 0..5 {
sum += i;
});
assert_eq!(sum, 10); // 0+1+2+3+4 = 10
// For loop with step value without else clause
let mut sum = 0;
pythonic_for!(i in 0..10, step = 2 {
sum += i;
});
assert_eq!(sum, 20); // 0+2+4+6+8 = 20
The else
clause executes if the loop finishes normally (i.e., not via a break
statement and no panics occurred).
use pythonic_for::pythonic_for;
// Basic for-else loop (else executes)
let mut found_val = -1;
pythonic_for!(i in 0..5 {
if i == 10 { // Condition never met
found_val = i;
break;
}
} else {
// Loop completed without break
found_val = 100;
});
assert_eq!(found_val, 100);
// For loop with break (else does not execute)
let mut found_val_break = -1;
pythonic_for!(i in 0..5 {
if i == 3 {
found_val_break = i;
break;
}
} else {
// This will not execute
found_val_break = 100;
});
assert_eq!(found_val_break, 3);
// Inclusive range
let mut sum = 0;
pythonic_for!(i in 1..=5 {
sum += i;
} else {
sum += 100;
});
assert_eq!(sum, 115); // 1+2+3+4+5+100 = 115
// Step value
let mut sum = 0;
pythonic_for!(i in 0..10, step = 2 {
sum += i;
} else {
sum += 100;
});
assert_eq!(sum, 120); // 0+2+4+6+8+100 = 120
// Negative step
let mut sum = 0;
pythonic_for!(i in 10..0, step = -2 {
sum += i;
} else {
sum += 100;
});
assert_eq!(sum, 130); // 10+8+6+4+2+100 = 130
// Iterating over a collection
let vec = vec![1, 2, 3, 4, 5];
let mut sum = 0;
pythonic_for!(i in vec {
sum += i;
} else {
sum += 100;
});
assert_eq!(sum, 115); // 1+2+3+4+5+100 = 115
The macro works with custom iterators as well:
# use pythonic_for::pythonic_for;
struct SquareIter {
current: i32,
end: i32,
}
impl Iterator for SquareIter {
type Item = i32;
fn next(&mut self) -> Option<Self::Item> {
if self.current <= self.end {
let result = self.current * self.current;
self.current += 1;
Some(result)
} else {
None
}
}
}
let square_iter = SquareIter { current: 1, end: 3 };
let mut sum = 0;
pythonic_for!(value in square_iter {
sum += value;
} else {
sum += 100;
});
assert_eq!(sum, 114); // 1+4+9+100 = 114
The pythonic_for!
macro works seamlessly with all standard Iterator adapters:
# use pythonic_for::pythonic_for;
// Using enumerate
let letters = vec!['a', 'b', 'c'];
let mut s = String::new();
pythonic_for!(pair in letters.iter().enumerate() {
let (idx, ch) = pair;
s.push_str(&format!("({},{})", idx, ch));
});
assert_eq!(s, "(0,a)(1,b)(2,c)");
// Using take to limit iterations
let numbers = vec![1, 2, 3, 4, 5, 6];
let mut sum_take = 0;
pythonic_for!(n in numbers.iter().take(3) {
sum_take += n;
});
assert_eq!(sum_take, 6); // 1+2+3
// Using skip to start from a specific position
let mut sum_skip = 0;
pythonic_for!(n in numbers.iter().skip(2) {
sum_skip += n; // 3+4+5+6
});
assert_eq!(sum_skip, 18);
// Using flat_map for nested collections
let nested = vec![vec![1, 2], vec![3, 4]];
let mut sum_flat = 0;
pythonic_for!(n in nested.iter().flat_map(|v| v.iter()) {
sum_flat += n;
});
assert_eq!(sum_flat, 10); // 1+2+3+4
// Using cycle with break for infinite iteration
let mut sum_cycle = 0;
let mut count = 0;
pythonic_for!(n in numbers.iter().cycle() {
sum_cycle += n;
count += 1;
if count >= 10 { // Iterate 10 times over 1,2,3,4,5,6...
break;
}
});
// (1+2+3+4+5+6) + (1+2+3+4) = 21 + 10 = 31
assert_eq!(sum_cycle, 31);
// Other adapters like filter_map, chain, zip, etc. are also supported
The pythonic_while!
macro provides Python-style while
loops, also with an optional else
clause that executes if the loop terminates normally (not via break
and no panics).
# use pythonic_for::pythonic_while;
let mut count = 0;
let mut sum = 0;
pythonic_while!(count < 3; { // Condition
// Body
sum += count;
count += 1;
});
assert_eq!(sum, 3); // 0 + 1 + 2
assert_eq!(count, 3);
# use pythonic_for::pythonic_while;
let mut count = 0;
let mut sum = 0;
pythonic_while!(count < 3; { // Condition
// Body
sum += count;
count += 1;
} else {
// Else clause: executes because loop completed normally
sum += 100;
});
assert_eq!(sum, 103); // 0 + 1 + 2 + 100
assert_eq!(count, 3);
// With break
let mut count_break = 0;
let mut sum_break = 0;
pythonic_while!(count_break < 5; {
if count_break == 2 {
break; // Loop terminates due to break
}
sum_break += count_break;
count_break += 1;
} else {
// Else clause: does NOT execute
sum_break += 100;
});
assert_eq!(sum_break, 1); // 0 + 1
assert_eq!(count_break, 2);
The pythonic_while!
macro also supports a do-while
style loop. The first block (do-body) always executes once. Then the condition is checked. If true, the second block (extra-body, or while-body) executes, and the loop continues.
# use pythonic_for::pythonic_while;
// do { body1 } while condition; { body2 }
let mut val = 0;
let mut iterations = 0;
pythonic_while!(do { // Do-body (always runs at least once)
val += 1;
} while val < 3; { // Condition (checked after do-body)
// Extra-body (runs if condition is true)
iterations += 1;
val += 1; // Modify condition variable here or in do-body
});
// Iteration 1: do{val=1}, cond(1<3 true), extra{iter=1, val=2}
// Iteration 2: do{val=3}, cond(3<3 false) -> loop ends
assert_eq!(val, 3);
assert_eq!(iterations, 1);
// do-while with else
let mut val_else = 0;
let mut iterations_else = 0;
let mut else_ran = false;
pythonic_while!(do {
val_else += 1;
} while val_else < 3; {
iterations_else += 1;
val_else += 1;
// Example break from extra_body
// if val_else == 2 { break; }
} else {
else_ran = true;
});
assert_eq!(val_else, 3);
assert_eq!(iterations_else, 1);
assert!(else_ran); // Else runs as loop terminated normally by condition
The pythonic_for!
and pythonic_while!
macros automatically handle break
statements. A break
inside the loop body will prevent the else
clause from executing.
# use pythonic_for::pythonic_for;
let mut sum = 0;
pythonic_for!(i in 0..5 {
if i == 3 {
break; // Exits loop, else clause is skipped
}
sum += i;
} else {
sum = 99; // This will not execute
});
assert_eq!(sum, 3); // 0+1+2
-
Native loop inside
pythonic_for!
/pythonic_while!
: Abreak
inside a native inner loop (e.g., a standard Rustfor
orwhile
) only breaks out of that inner native loop. The outer pythonic loop continues, and itselse
clause will execute if the pythonic loop itself completes normally.# use pythonic_for::pythonic_for; let mut outer_sum = 0; let mut inner_breaks = 0; pythonic_for!(i in 0..2 { // Outer pythonic loop outer_sum += i; for j in 0..5 { // Inner native loop if j == 1 { inner_breaks += 1; break; // This only breaks from the inner native loop } } } else { // This will still execute, as the pythonic_for loop completed normally. outer_sum += 100; }); assert_eq!(inner_breaks, 2); // Inner loop broke twice assert_eq!(outer_sum, 101); // 0 (i=0) + 1 (i=1) + 100 (else) = 101
-
Nested
pythonic_for!
/pythonic_while!
loops: Abreak
statement inside an inner pythonic loop will prevent its ownelse
clause from executing. However, it does not automatically break the outer pythonic loop. The outer loop will continue its execution. If the outer loop completes normally (i.e., is not itself broken out of), itselse
clause will execute.# use pythonic_for::pythonic_for; let mut outer_sum = 0; let mut inner_sum = 0; let mut outer_else_ran = false; let mut inner_else_count = 0; pythonic_for!(i in 0..2 { // Outer pythonic loop outer_sum += i; pythonic_for!(j in 0..3 { // Inner pythonic loop inner_sum += j; if i == 0 && j == 1 { // Breaking only the inner pythonic loop break; } } else { // Else for inner pythonic loop // Will run if inner loop is not broken. inner_else_count += 1; }); // Outer loop continues here } else { // Else for outer pythonic loop outer_else_ran = true; outer_sum += 100; }); // Outer loop (i=0): outer_sum=0. // Inner loop (j=0, j=1, break): inner_sum = 0+1=1. Inner else does not run. // Outer loop (i=1): outer_sum=0+1=1. // Inner loop (j=0, j=1, j=2, completes normally): inner_sum = 1 + (0+1+2) = 1+3=4. Inner else runs. inner_else_count=1. // Outer loop completes normally. Outer else runs: outer_else_ran=true, outer_sum = 1+100=101. assert_eq!(outer_sum, 101); assert_eq!(inner_sum, 4); assert_eq!(inner_else_count, 1); // Inner else ran once (for i=1) assert!(outer_else_ran);
This crate provides consistent error handling mechanisms for both pythonic_for!
and pythonic_while!
.
-
Panic Handling: If a panic occurs within the body of a
pythonic_for!
orpythonic_while!
loop, the execution of the loop is immediately halted. Theelse
clause associated with that loop will not be executed. The panic will propagate as usual unless caught by an outerstd::panic::catch_unwind
.# use pythonic_for::pythonic_for; # use std::panic; let mut else_executed = false; let result = panic::catch_unwind(|| { pythonic_for!(i in 0..5 { if i == 2 { panic!("Simulated error!"); } } else { else_executed = true; }); }); assert!(result.is_err()); // The panic was caught assert!(!else_executed); // Else clause was skipped
-
Result-Based Error Handling: For more controlled error management, you can use standard Rust
Result
types within the loop body. If an operation returns anErr
, you canbreak
from the loop. Theelse
clause will not execute if the loop was terminated by abreak
.# use pythonic_for::pythonic_for; fn process_item(item: i32) -> Result<i32, String> { if item == 3 { Err(format!("Failed on item {}", item)) } else { Ok(item * 2) } } let mut processed_sum = 0; let mut operation_status: Result<(), String> = Ok(()); let mut else_ran = false; pythonic_for!(i in 0..5 { match process_item(i) { Ok(value) => { processed_sum += value; } Err(e) => { operation_status = Err(e); break; // Exit the loop on first error } } } else { // This only executes if all items were processed successfully (no break) else_ran = true; }); assert!(else_ran || operation_status.is_err()); // Else runs OR an error occurred if operation_status.is_ok() { assert!(else_ran); assert_eq!(processed_sum, (0*2) + (1*2) + (2*2) + (4*2)); // 0+2+4+8 = 14 } else { assert!(!else_ran); assert_eq!(processed_sum, (0*2) + (1*2) + (2*2)); // 0+2+4 = 6, then item 3 errors assert_eq!(operation_status, Err("Failed on item 3".to_string())); }
When using infinite iterators (e.g., std::iter::repeat
or Iterator::cycle()
) with pythonic_for!
, the else
clause is generally unreachable unless a break
condition is included within the loop body.
# use pythonic_for::pythonic_for;
let data = [1,2,3];
let mut iteration_count = 0;
// LOGICAL ERROR if no break: The else clause might never execute with cycle()
// pythonic_for!(n in data.iter().cycle() {
// // This will cycle through the collection indefinitely
// } else {
// // This code is unreachable without a break in the loop body
// });
// CORRECT: Use a break condition with infinite iterators
pythonic_for!(n in data.iter().cycle() {
iteration_count += 1;
if iteration_count > 5 { // Ensure the loop terminates
break;
}
} else {
// This is reachable if the break condition above was NOT met (which it will be here).
// If break is hit, this else is skipped.
});
assert_eq!(iteration_count, 6); // Loop runs 6 times (0..=5) then breaks.
Licensed under either of:
- Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.