polonius_the_crab

Macro polonius_loop

Source
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`

§breaks

Whilst return and continues 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 breaking i, an integer/index, that is, a non-dependent value.

  • this wouldn’t require polonius{,_the_crab} if it weren’t for the return 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-infected break type annotation for the proper lifetimes to come into play:

      polonius_loop!(|var| -> … , break: … {

  • Else, use polonius_break! (in which case the break 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()
    }

  1. In practice, with polonius_break_dependent! we won’t be able to reuse coll 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 that break with a return