Question about loops, borrows and early returns

I'm trying to write code that mutates a buffer until it can be deserialized.
I'd like to not have to write a seperate is_valid function but instead start deserializing right away and retry on error.

However borrowck is being a bully.

Here's a minimal example:

struct Foo<'a> {
    str: &'a str,
}

impl Foo<'_> {
    fn try_new(buf: &[u8]) -> Option<Foo> {
        let str = core::str::from_utf8(buf).ok()?;
        Some(Foo { str })
    }
}

fn foo(buf: &mut [u8]) -> Foo {
    loop {
        if let Some(foo) = Foo::try_new(buf) {
            return foo;
        }
        buf.rotate_left(1)
    }
}

And the error I get:

fn foo(buf: &mut [u8]) -> Foo {
            - let's call the lifetime of this reference `'1`
    loop {
        if let Some(foo) = Foo::try_new(buf) {
                                        --- immutable borrow occurs here
            return foo;
                   --- returning this value requires that `*buf` is borrowed for `'1`
        }
        buf.rotate_left(1)
        ^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

Since I don't feel like waiting for Polonius, any suggestions on what can be done?

If you just want to bypass the borrow checker for now, you can replace the reference with a raw pointer, and then switch back whenever Polonius is stable:

fn foo(buf: &mut [u8]) -> Foo {
    loop {
        unsafe {
            let buf = buf as *mut [u8];
            if let Some(foo) = Foo::try_new(&*buf) {
                return foo;
            }
        }
        buf.rotate_left(1)
    }
}

But instead I would probably add an infallible or unchecked constructor to Foo, so after doing UTF-8 validation, we can construct a Foo using a separate call to str::from_utf8_unchecked. Something like this:

impl Foo<'_> {
    fn new(str: &str) -> Foo {
        Foo { str }
    }
}

fn foo(buf: &mut [u8]) -> Foo {
    loop {
        if let Some(_) = std::str::from_utf8(buf) {
            let str = unsafe { std::str::from_utf8_unchecked(buf) };
            return Foo::new(str);
        }
        buf.rotate_left(1)
    }
}

If the strings are short and you don't mind running UTF-8 validation twice on each string, you can eliminate the unsafe code and just call str::from_utf8 (or Foo::try_new) twice.

2 Likes

Thanks for the suggestions.
If nothing else works I'll go with calling Foo::try_new twice since performance isn't super critical and it avoids unsafe.
I was just hoping there would be a way to tell the borrow checker what I'm doing is ok.