Rust lifetime in cycle

How to get this example to compile without copying an array or multiple calls b()

per iteration - b()

has to do some expensive parsing?

This is not the complete code I wrote, but it illustrates the problem I had. Here's Test

trying to do some thread parsing work. c()

is a parsing function, it returns Some

when the parsing was successful. b()

is a function that tries to read more data from a stream when c()

it cannot parse using the available data. The return value is a chunk in self.v

containing the parsed range.

struct Test {
    v: [u8; 10],
    index: u8,
}

impl Test {
    fn b(&mut self) {
        self.index = 1
    }

    fn c(i: &[u8]) -> Option<&[u8]> {
        Some(i)
    }

    fn a(&mut self) -> &[u8] {
        loop {
            self.b();

            match Test::c(&self.v) {
                Some(r) => return r,
                _ => continue,
            }
        }
    }
}

fn main() {
    let mut q = Test {
        v: [0; 10],
        index: 0,
    };

    q.a();
}

      

On compilation, it produces the following check validation error:

error[E0502]: cannot borrow `*self` as mutable because `self.v` is also 
borrowed as immutable
  --> <anon>:17:13
   |
17 |             self.b();
   |             ^^^^ mutable borrow occurs here
18 | 
19 |             match Test::c(&self.v) {
   |                            ------ immutable borrow occurs here
...
24 |     }
   |     - immutable borrow ends here

      

If I change a()

to:

fn a(&mut self) -> Option<&[u8]> {
    loop {
        self.b();

        if let None = Test::c(&self.v) {
            continue
        }

        if let Some(r) = Test::c(&self.v) {
            return Some(r);
        } else {
            unreachable!();
        }
    }
}

      

It then runs, but with the obvious disadvantage of calling the parse function c()

twice.

I understand that changing self

while the return value depends on it is unsafe, however I don't understand why the immutable loan for self.v

is still alive in the next iteration when we tried to call b()

again.

+3


source to share


1 answer


Right now "Rustc cannot" handle "conditional borrowing". See comment from Gankro at 21906 .

It cannot assign the correct lifetime to borrow if only one execution path completes the cycle.

I can suggest this workaround, but I'm not sure if it's optimal:

fn c(i: &[u8]) -> Option<(usize, usize)> {
    Some((0, i.len()))
}

fn a(&mut self) -> &[u8] {
    let parse_result;
    loop {
        self.b();

        match Test::c(&self.v) {
            Some(r) => {
                parse_result = r;
                break;
            }
            _ => {}
        }
    }
    let (start, end) = parse_result;
    &self.v[start..end]
}

      



You can plot the parsing result using array indices and convert them to references outside of the loop.

Another option is to resort to unsafe

to split the lifetime. I am not an expert on the safe use of unsafe, so pay attention to the comments of others.

fn a(&mut self) -> &[u8] {
    loop {
        self.b();

        match Test::c(&self.v) {
            Some(r) => return unsafe{ 
                // should be safe. It decouples lifetime of 
                // &self.v and lifetime of returned value,
                // while lifetime of returned value still
                // cannot outlive self
                ::std::slice::from_raw_parts(r.as_ptr(), r.len())
            },
            _ => continue,
        }
    }
}

      

+4


source







All Articles