Macro tear::twist [−][src]
Breaks loops (or not) based on the Looping
variant
Usage
The general syntax is the following:
// With $e an expression of type `Looping`
twist! { [-val] $e }
twist! { [-val] -with $label | $e }
twist! { [-box] [-val $type,] -label <$label [: $type]>,* | $e }
// Same, but with $e implementing Judge, and $f a function that maps the Bad value to Looping
twist! { [-val] $e => $f }
twist! { [-val] -with $label | $e => $f }
twist! { [-box] [-val $type,] -label <$label [: $type]>,* | $e => $f }
Use cases
If you’re breaking from the current loop, use one of the following
twist! { $e } // Usual case
twist! { -val $e } // If you're breaking with a value (`loop` loop)
If you’re breaking a labeled loop:
twist! { -with 'label | $e } // Normal break from the labeled loop
twist! { -val -with 'label | $e } // If you're breaking the labeled loop with a value
If you’re breaking from multiple loops:
twist! { -label 'a, 'b | $e } // Normal break for loops 'a, 'b and innermost
If you’re breaking from multiple loops and can break with the same value type:
// If the innermost loop is a normal break
twist! { -label 'a: i32, 'b, 'c: i32 | $e }
// If the innermost loop breaks with a value (the type is mandatory)
twist! { -val i32, -label 'a:i32, 'b | $e }
If you’re breaking from multiple loops with multiple types by using Box<dyn Any>
as the value type:
// If the innermost loop is a normal break
twist! { -box -label 'a: i32, 'b: String | $e }
// If the innermost loop breaks with a value
twist! { -box -val i32, -label 'a, 'b: String | $e }
If you want to extract a value (eg. Result
or Option
) and break/continue otherwise:
twist! { $e => $f }
// Or any of the previous ones with `$e => $f` instead of `$e`
with $e your value (that implements Judge) and $f the mapping function from the Bad type
to a Looping
value.
Description
twist!
takes an expression of Looping
type, and break
s, continue
s or resume the loop
execution based on the Looping
variant. There are various flags that control which loop are
concerned, and what value type to break with (for loop
loops).
Normally, you can only break with a single type because it is the B
parameter for
Looping::<_ B>
. But if we use Box<dyn Any>
, a trait object, and then we downcast to the
correct concrete type, we can break with multiple types.
The -box
option tells twist!
to expect a break type of Box<dyn Any>
and to attempt to
downcast to the type specified by -val
or -label
before breaking the loop.
The mapping syntax $e => $f
is used to simplify “good value” handling in loops. $e
implements
Judge, and $f
maps the bad type of $e
to a Looping
value.
For example, you generally want to skip the current loop iteration if you get an Err(_)
from a function call. To do so, you would either use if let
and
have the happy path indented in the if let
body, or you could add the following match
statement before the rest of your code:
let wanted_value = match try_get_value() { Ok(v) => v, Err(_) => continue, };
The mapping syntax lets you simplify that “guard” statement to the following:
let wanted_value = twist! { try_get_value() => |_| next!() };
Errors
Compile failure
A common error (at least for me) is to forget that you need to specify if the innermost loop breaks with a value or not, even if you don’t do anything with it. Similarly, you always need to specify the types of the loop labels.
Panics
This will panic if you use the wrong loop label index; if you try to break a
non-loop
loop with a value; or if you try to break a loop
-loop that expects a value,
without a value
Examples
All example bring twist
and Looping
into scope.
An infinite loop that immediately gets broken.
loop { twist! { Looping::Break { label: None } } }
Breaking a loop with a value with the -val
switch.
let x = loop { twist! { -val Looping::BreakVal { label: None, value: 8 } } }; assert_eq![ x, 8 ];
Breaking a labeled loop. -with
sets the loop on which we act.
'a: loop { loop { twist! { -with 'a | Looping::Break { label: None } } } }
Breaking multiple loop with different types with -box
. Labels are counted from 0, so Some(0)
refers to 'a: String
. The second loop also breaks with a value type of i32
, specified in
twist!
as -val i32,
.
use tear::anybox; let x = 'a: loop { let _ = loop { twist! { -box -val i32, -label 'a: String | Looping::BreakVal { label: Some(0), value: anybox!("a".to_string()) } } }; }; assert_eq![ x, "a".to_string() ];
See more barebones examples for breaking multiple loops in test/label.rs
.
See also
- The
last!
,next!
andresume!
utility macros. - The
anybox!
macro when the expression is of typeBox<dyn Any>
and we unbox it
Developer docs
See inline comments for more information.
Most patterns of the macro are the entrypoints for 2 “templated” implementations for
“single loop break” (@single
) and “labeled loop break” (@boxed
).
@boxed
: Breaking from multiple loops
The non-box
versions can only break with a single value type because you can only choose one type
to be the BreakVal
value type. To circumvent this with the box
versions, we expect
a Box<dyn Any>
value that we downcast to the right type.
@single
: Breaking from a single loop
When breaking from a single loop without a value, we set the BreakVal type of Looping
to BreakValError
. If the user tries to break with a value, the program will fail to compile
because the types are different. It should then display the full name of BreakValError
(which is an error message) in the error message.