#polonius #workaround #borrow

polonius_workaround

Safe replacement for Polonius on stable Rust

1 unstable release

Uses old Rust 2015

0.1.0 Feb 8, 2022

#6 in #workaround

MIT license

12KB

Polonius_workaround

Crate docs

This crate provides simple and logical safe api over the unsafe workarounds for borrow checker limitations that will be solved by Polonius.

License

MIT


lib.rs:

This crate provides api to solve borrow checker errors caused by limitation of current rust borrow checked. There exists next version of rust borrow checker called Polonius that is supposed to remove such limitations, but it is permanently unstable with no current plans for stabilization. And quite often the only way to work around those limitations on stable rust without some kind of regression is with unsafe code. But it is annoying to be required to use unsafe for such simple things, and it is too easy to get wrong, not to mention it is just delayed bomb waiting for some uncareful refactoring to release it.

All functionality is provided via PoloniusExt extension trait. It has 3 methods:

  • try_get_with/try_get_mut_with work for simple cases where you need to return shared/mutable reference respectively. It should just work and in most cases that is enough. But sometimes you need to return not a reference but some another type that contains a reference. Thats when you need
  • try_get_with2 It allows you to return any type with reference inside, but due to rust type inference bugs around HRTBs it is a bit annoying to use. See its docs for more details.

As far as author knows it allows to solve any king of borrow checker issues that would be solved by Polonius. Although you still need to be confident enough in Rust to know that your code is actually correct and just not accepted by current borrow checker. So although with this crate you do not actually need Polonius anymore, it is still a nice thing to have in general.

The code compiles since Rust 1.0 but but try_get_with2 actually works only since Rust 1.41.

And here is a real example that you couldn't work around without significant performance regression or unsafe.

trait LinkedListExt<T> {
    fn find_or_create<F, C>(&mut self, predicate: F, create: C) -> &mut T
    where
        F: FnMut(&T) -> bool,
        C: FnMut() -> T;
}

impl<T: 'static> LinkedListExt<T> for LinkedList<T> {
    fn find_or_create<F, C>(&mut self, mut predicate: F, mut create: C) -> &mut T
    where
        F: FnMut(&T) -> bool,
        C: FnMut() -> T,
    {
        if let Some(x) = self.iter_mut().find(|e| predicate(&*e)){
            return x;
        }
        self.push_back(create());
        self.back_mut().unwrap()
    }
}

Now with this crate you just wrap two branches into a closures then call try_get_mut_with and voila - it works

impl<T: 'static> LinkedListExt<T> for LinkedList<T> {
    fn find_or_create<F, C>(&mut self, mut predicate: F, mut create: C) -> &mut T
    where
        F: FnMut(&T) -> bool,
        C: FnMut() -> T,
    {
        self.try_get_mut_with(|x| x.iter_mut().find(|e| predicate(&*e)))
            .unwrap_or_else(|x| {
                x.push_back(create());
                x.back_mut().unwrap()
            })
    }
}

No runtime deps