Returning a wrapper type with lifetime, compiler says mutable borrow more than once, but I think they are in different control flow paths

sorry I coudn't think of a more concise title to better describe the problem, probably because I don't really understand the problem.

I was writing wrapper types for some third party library, which has a Resource type and a guard type to actually call the methods on. I want to wrap both of them to provide some extra check or transformation for certain operations.

the code looks like:

// these are from extern crate

struct Resource {}

struct Token<'a> {
    resource: &'a mut Resource,
}

impl Resource {
    fn try_grant<'a>(&'a mut self) -> Option<Token<'a>> {
        // check availability
        Some(Token {
            resource: self
        })
    }
}

// these are my wrappers
struct ResourceWrapper {
    inner: Resource,
}

struct TokenWrapper<'a> {
    inner: Token<'a>,
}

impl ResourceWrapper {
     fn grant<'a>(&'a mut self) -> TokenWrapper<'a> {
        if let Some(token) = self.inner.try_grant() {
            return TokenWrapper {
                inner: token,
            }
        }
        self.wait_ready();
        if let Some(token) = self.inner.try_grant() {
            TokenWrapper {
                inner: token,
            }
        } else {
            unreachable!()
        }
    }
    fn wait_ready(&mut self) {
        // locking and synchronization stuff
    }
}

this code gives: error[E0499]: cannot borrow `*self` as mutable more than once at a time --- specifically, the line return TokenWrapper{...} under the if let branch prevents self from being used again. I really don't understand why it is the case, and how to express this (prefer safe code, but unsafe is acceptable if I have to). any suggestions are appreciated.

here is the same code snippet on rust playground if you want to see the actual compiler message or play with it

It's a known limitation to borrow checker. It happens when you return a mutable reference in one branch and use it in another. The easy way out is just using unsafe.

1 Like

thanks for the information. is there's a tracking issue I can follow about the limitation?

This issue or one of the related ones linked within.

2 Likes

There’s even an experimental implementation of an improved borrow checker called β€œpolonius”. E.g. see it used with your example here in the Compiler Explorer (experimental flag β€œ-Z polonius” on nightly rustc to use it).

2 Likes

With polonius_the_crab - Rust, your code is allowed to pass in stable Rust:

https://www.rustexplorer.com/b/ib10vm

    fn grant<'a>(&'a mut self) -> TokenWrapper<'a> {
        let mut wrapper = self;
        polonius!(|wrapper| -> TokenWrapper<'polonius> {
            if let Some(token) = wrapper.inner.try_grant() {
                polonius_return!(TokenWrapper { inner: token })
            }
        });
        wrapper.wait_ready();
        polonius!(|wrapper| -> TokenWrapper<'polonius> {
            if let Some(token) = wrapper.inner.try_grant() {
                polonius_return!(TokenWrapper { inner: token });
            } else {
                unreachable!()
            }
        })
    }
3 Likes

The second usage of polonius! seems superfluous, this works aswell

    fn grant<'a>(&'a mut self) -> TokenWrapper<'a> {
        let mut wrapper = self;
        polonius!(|wrapper| -> TokenWrapper<'polonius> {
            if let Some(token) = wrapper.inner.try_grant() {
                polonius_return!(TokenWrapper { inner: token });
            }
        });
        wrapper.wait_ready();
        if let Some(token) = wrapper.inner.try_grant() {
            return TokenWrapper { inner: token };
        } else {
            unreachable!()
        }
    }
2 Likes

thank you guys so much. these are valuable information to me. just read the docs of polunius-the-crab and learned a lot about the borrow checker

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.