Macro tear::twist[][src]

macro_rules! twist {
    ( -label $($tokens:tt)* ) => { ... };
    ( -val $type:ty, -label $($tokens:tt)* ) => { ... };
    ( -box -label $($tokens:tt)* ) => { ... };
    ( -box -val $type:ty, -label $($tokens:tt)* ) => { ... };
    ( @boxed ( ($($bk:tt)?) ($($bv:ty)?) ($($bx:ty)?) )         // Flags
		( $( ($c:expr, $l:lifetime) )* )                        // Breaks
		[ ($( ($count:expr,  $label:lifetime,  $type:ty)  )*)   // Normal breakvals
		  ($( ($bcount:expr, $blabel:lifetime, $btype:ty) )*) ] // Boxed breakvals
		$e:expr
	) => { ... };
    ( @single
		[$( ($breaker:tt) ($($label:lifetime)?) )?]   // Break
		[$( ($breakval:tt) ($($vlabel:lifetime)?) )?] // BreakVal
		($e:expr)
	) => { ... };
    ( -with $l:lifetime | $($tokens:tt)* ) => { ... };
    ( -val -with $l:lifetime | $($tokens:tt)* ) => { ... };
    ( -val $($tokens:tt)* ) => { ... };
    ( $($tokens:tt)* ) => { ... };
}

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 breaks, continues 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! and resume! utility macros.
  • The anybox! macro when the expression is of type Box<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.