Though this be madness, yet there is method in 't.

stable-rust-stands-atop-dead-zpolonius

More context

  1. Hamlet:

    For yourself, sir, shall grow old as I am – if, like a crab, you could go backward.

  2. Polonius:

    Though this be madness, yet there is method in 't.

  3. Polonius, eventually:

    polonius-lying-dead

::polonius-the-crab

Tools to feature more lenient Polonius-based borrow-checker patterns in stable Rust.

Repository Latest version Documentation MSRV unsafe forbidden License CI

Rationale: limitations of the NLL borrow checker

See the following issues:

All these examples boil down to the following canonical instance:

```rust ,compile_fail

![forbid(unsafe_code)]

use ::std::{ collections::HashMap, };

/// Typical example of lack-of-Polonius limitation: getorinsert pattern. /// See https://nikomatsakis.github.io/rust-belt-rust-2019/#72 fn getorinsert( map: &mut HashMap, ) -> &String { if let Some(v) = map.get(&22) { return v; } map.insert(22, String::from("hi")); &map[&22] } ```

error message

console error[E0502]: cannot borrow `*map` as mutable because it is also borrowed as immutable --> src/lib.rs:53:5 | 14 | map: &mut HashMap<u32, String>, | - let's call the lifetime of this reference `'1` 15 | ) -> &String { 16 | if let Some(v) = map.get(&22) { | --- immutable borrow occurs here 17 | return v; | - returning this value requires that `*map` be borrowed for `'1` 18 | } 19 | map.insert(22, String::from("hi")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

Explanation

Click to see

Now, this pattern is known to be sound / a false positive from the current borrow checker, NLL.

Workarounds

So "jUsT uSe UnSaFe" you may suggest. But this is tricky:

Non-unsafe albeit cumbersome workarounds for lack-of-Polonius issues

Click to see

While you should try these workarounds first and see how they apply to your codebase, sometimes they're not applicable or way too cumbersome compared to "a tiny bit of unsafe".

In that case, as with all the cases of known-to-be-sound unsafe patterns, the ideal solution is to factor it out down to its own small and easy to review crate or module, and then use the non-unsafe fn API thereby exposed 👌.

Enters ::polonius-the-crab

polonius-the-crab

Explanation of its implementation

Click to see

So, back to that "safety encapsulation" idea:

  1. let's find a canonical instance of this borrow checker issue that is known to be sound and accepted under Polonius;

  2. and tweak it so that it can then be re-used as a general-purpose tool for most of these issues.

And if we stare at the borrow checker issues above, we can see there are two defining ingredients:

The issue is then that that second branch ought to get back access to the stuff borrowed in the first branch, but the current borrow checker denies it.

That's where we'll sprinkle some correctly-placed unsafe to make the "borrow checker look the other way" just for a moment, the right moment.

This thus gives us (in pseudo-code first):

``rust, ignore fn polonius<'r, T> ( borrow: &'r mut T, branch: impl // generic type to apply to all possible scopes. for<'any> // <- higher-order lifetime ensures the&mut Tinfected with it… FnOnce(&'any mut T) // …can only escape the closure… // vvvv … through its return type and its return type only. -> Option< _<'any> > // <- TheSome/Nonediscriminant represents the branch info. // ^^^^^^^ // some return type allowed to depend on'any. // For instance, in the case ofgetorinsert, this could // have been&'any String(orOption<&'any String>). // Bear with me for the moment and tolerate this pseudo-code. , ) -> Result< // <- we "forward the branch", but with data attached to the fallback one (Err(…)). _<'r>, // <- "plot twist":'anyabove was'r! &'r mut T, // <- through Arcane Magic™ we get to transmute theNoneinto anErr(borrow) > { let tentative_borrow = &mut *borrow; // reborrow if let Some(dependent) = branch(tentative_borrow) { /* within this branch, the reborrow needs to last for'r` / return Ok(dependent); } / but within this branch, the reborrow needs to have ended: only Polonius supports that kind of logic */

// give the borrow back
Err(borrow) // <- without Polonius this is denied

} ```

This function, ignoring that generic unspecified _<'…> return type in pseudo-code, does indeed represent a canonical example of the borrow checker issue (without -Zpolonius, it will reject the Err(borrow) line saying that borrow needs to be borrowed for 'r so that dependent is, and that 'r spans until any end of function (the borrow checker bug).

Whereas with -Zpolonius it is accepted.

The ArcaneMagic™

The correct use of unsafe, here, to palliate the lack of -Zpolonius, is to change:

rust, ignore let tentative_borrow = &mut *borrow; // reborrow

into:

rust ,ignore let tentative_borrow = unsafe { &mut *(borrow as *mut _) }; // reborrow

where unsafe { &mut *(thing as *mut _) } is the canonical way to perform lifetime(-of-the-borrow) extension: the lifetime of that &mut borrow is then no longer tied, in any way, to 'r nor to *borrow.

The borrow checker no longer holds our hand, as far as overlapped usage of borrow and tentative_borrow is concerned (which would be UB). It is now up to us to ensure no runtime path can ever lead to such borrows overlapping.

And indeed they don't, as the simple branch showcases:

In other words:

Though this be unsafe, yet there is soundness in 't.

As an extra precaution, this crate does even guard that usage of unsafe through a cfg-opt-out, so that when using -Zpolonius, the unsafe is removed, and yet the body of the function, as well as its signature, compiles fine (this is further enforced in CI through a special test).

Generalizing it

None becomes <Err>

It turns out that we don't have to restrict the branch to returning no data on None, and that we can use it as a "channel" through which to smuggle non-borrowing data.

This leads to replacing Option< _<'any> > with Result< _<'any>, Err >

The FnOnceReturningAnOption trick is replaced with a HKT pattern

Indeed, a FnOnceReturningAnOption-based signature would be problematic on the caller's side, since:

So this leads to a situation where both the caller and callee expect each other to disambiguate what the higher-order return value of the closure should be, leading to no higher-orderness to begin with and/or to type inference errors.

So that _<'any> is achieved in another manner. Through HKTs, that is, through "generic generics" / "generics that are, themselves, generic":

rust ,ignore //! In pseudo-code: fn polonius<'r, T, Ret<'_>> ( borrow: &'r mut T, branch: impl FnOnce(&'_ mut T) -> Option<Ret<'_>>, ) -> Result< Ret<'r>, &'r mut T, >

This cannot directly be written in Rust, but you can define a trait representing the <'_>-ness of a type (HKT in this crate), and with it, use as WithLifetime<'a>::T as the "feed <'a>" operator:

rust ,ignore //! Real code! fn polonius<'r, T, Ret : HKT> ( borrow: &'r mut T, branch: impl FnOnce(&'_ mut T) -> Option< <Ret as WithLifetime<'_>>::T >, ) -> Result< <Ret as WithLifetime<'r>>::T, &'r mut T, >

We have reached the definition of the actual fn polonius exposed by this very crate!

Now, a HKT type is still cumbersome to use. If we go back to that get_or_insert example that was returning a &'_ String, we'd need to express that "generic type" representing <'lt> => &'lt String, such as:

``rust ,ignore /// Pseudo-code (StringRefis not a type,StringRef<'…>` is). type StringRef<'any> = &'any String;

/// Real HKT code: make StringRef a fully-fledged stand-alone type struct StringRef; /// And now express the <'lt> => &'lt String relationship: impl<'lt> WithLifetime <'lt> for StringRef // is: ⇓ { // ⇓ // ⇓ type T = &'lt String ; } ```

Putting it altogether: get_or_insert with no .entry() nor double-lookup

So this crate exposes a "raw" polonius() function that has the unsafe in its body, and which is quite powerful at tackling these lack-of-polonius related issues.

And yet, it is cumbersome to use:

```rust use ::poloniusthecrab::polonius;

fn getorinsert ( map: &'_ mut ::std::collections::HashMap, ) -> &'_ String { #![forbid(unsafe_code)] // No unsafe code in this function: VICTORY!!

enum StringRef {}
impl<'lt> ::polonius_the_crab::WithLifetime<'lt> for StringRef {
    type T = &'lt String;
}

match polonius::<StringRef, _, _, _>(map, |map| map.get(&22).ok_or(())) {
    | Ok(ret) => {
        // no second-lookup!
        ret
    },
    // we get the borrow back (we had to give the original one to `polonius()`)
    | Err((map, ())) => {
        map.insert(22, String::from("…"));
        &map[&22]
    },
}

} ```

Hence why this crate also offers

Convenient macros for ergonomic usage 😗👌

Mainly, a polonius! entry point, within which you can use polonius_return! to early return the dependent value, or a polonius_break! to instead "break" / leave the polonius! block with a non-dependent value (notice how the branch nature of this borrow checker limitation is kept in the very bones of the API).

This leads to the following get_or_insert usage:

Using Polonius The Crab for Fun And Profit™

polonius-the-crab

```rust

![forbid(unsafe_code)]

use { ::poloniusthecrab::{ prelude::*, }, ::std::{ collections::HashMap, }, };

/// Typical example of lack-of-Polonius limitation: getorinsert pattern. /// See https://nikomatsakis.github.io/rust-belt-rust-2019/#72 fn getorinsert( mut map: &mut HashMap, ) -> &String { // Who needs the entry API? polonius!(|map| -> &'polonius String { if let Some(v) = map.get(&22) { polonius_return!(v); } }); map.insert(22, String::from("hi")); &map[&22] } ```