macro_rules! polonius_loop { ( | $var:ident $(,)? | -> $Ret:ty $(, break: $Break:ty)? $body:block $(,)? ) => { ... }; }
Expand description
Convenience support for the loop { … polonius!(…) }
pattern.
§Example
-
#![forbid(unsafe_code)] use { ::polonius_the_crab::{ prelude::*, }, ::std::{ collections::HashMap, }, }; enum Value { Alive(i32), Daed, } // Notice how this example, *despite its usage of the fancy `.entry()` API // of `HashMap`s*, still needs `polonius_the_crab` to work! fn get_first_alive_from_base_or_insert ( mut map: &'_ mut HashMap<usize, Value>, base: usize, default_value: i32, ) -> &'_ i32 { let mut idx = base; // (loop { polonius_loop!(|map| -> &'polonius i32 { use ::std::collections::hash_map::*; // return( polonius_return!( match map.entry(idx) { | Entry::Occupied(entry) => match entry.into_mut() { // Found a value! | &mut Value::Alive(ref val) => val, // "tombstone", keep searching | &mut Value::Daed => { idx += 1; // continue; polonius_continue!(); }, }, | Entry::Vacant(slot) => match slot.insert(Value::Alive(default_value)) { | &mut Value::Alive(ref val) => val, | &mut Value::Daed => unreachable!(), }, } ); }); unreachable!(); }
Error message without ::polonius_the_crab
-
ⓘ
error[E0499]: cannot borrow `*map` as mutable more than once at a time --> src/lib.rs:222:18 | 22 | mut map: &'_ mut HashMap<usize, Value>, | - let's call the lifetime of this reference `'1` ... 33 | match map.entry(idx) { | ^^^ `*map` was mutably borrowed here in the previous iteration of the loop ... 45 | | &mut Value::Alive(ref val) => val, | --- returning this value requires that `*map` be borrowed for `'1`
§break
s
Whilst return
and continue
s inside a polonius_loop!
invocation are
quite straight-forward, break
is actually more subtle and difficult
to use.
Click to show
Indeed, compare the break
semantics of the following two snippets:
-
ⓘ
let mut i = 0; let found = loop { match collection.get_mut(&i) { Some(entry) => if entry.is_empty() { break entry; // 👈 } else { // … }, None => i += 1, } };
vs.
-
ⓘ
let mut i = 0; let position = loop { match collection.get_mut(&i) { Some(entry) => if entry.is_empty() { break i; // 👈 } else { // this requires polonius{,_the_crab} btw return entry; }, None => i += 1, } };
With the former, we have a dependent / borrowing-from-collection
entry
value, which is the one we want to break
:
- this requires
polonius{,_the_crab}
(independently of the presence of return-of-dependent-value statements);
Whereas with the latter, we are break
ing i
, an integer/index, that is,
a non-dependent value.
- this wouldn’t require
polonius{,_the_crab}
if it weren’t for thereturn entry
statement which does return a dependent item.
So, with the former, we can’t use collection
while entry
is alive1 ,
whereas with the latter we perfectly can.
All these differences, which are type-system-based, represent information
which is unaccessible for the polonius…!
family of macros, so a
single/unified polonius_break!
macro for both things, for instance, would
be unable to make such a difference: unnecessary compile errors would then
ensue!
The solution is then to feature not one but two break
-ing macros,
depending on whether the value which we want to break depends on/borrows
the &'polonius mut
-borrowed state.
-
If yes, use
polonius_break_dependent!
;-
this, in turn, requires an additional
'polonius
-infectedbreak
type annotation for the proper lifetimes to come into play:polonius_loop!(|var| -> … , break: … {
-
-
Else, use
polonius_break!
(in which case thebreak
type annotation should not be used).
§Examples
-
use { ::polonius_the_crab::{ prelude::*, }, ::std::{ collections::HashMap, }, }; fn break_entry (mut coll: &'_ mut HashMap<i32, String>) -> &'_ mut String { let mut i = 0; // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv let found = polonius_loop!(|coll| -> _, break: &'polonius mut String { match coll.get_mut(&i) { Some(entry) => if entry.is_empty() { polonius_break_dependent!(entry); // 👈 } else { // … }, None => i += 1, } }); found.push('!'); found }
vs.
-
use { ::polonius_the_crab::{ prelude::*, }, ::std::{ collections::HashMap, }, }; fn break_index (mut coll: &'_ mut HashMap<i32, String>) -> &'_ mut String { let mut i = 0; let position = polonius_loop!(|coll| -> &'polonius mut String { match coll.get_mut(&i) { Some(entry) => if entry.is_empty() { polonius_break!(i); // 👈 } else { polonius_return!(entry); }, None => i += 1, } }); // Re-using `coll` is fine if not using the `dependent` flavor of break. coll.get_mut(&i).unwrap() }
In practice, with
polonius_break_dependent!
we won’t be able to reusecoll
anymore in the function. If this is a problem for you, you’ll have no other choice but to refactor your loop into a smaller helper function so as to replace thatbreak
with areturn
. ↩