Gurwinder Singh
Gurwinder Singh

Reputation: 39477

Cannot borrow as mutable in loop

I have the following code:

pub fn read_packet<'a>(buf: &'a mut [u8]) -> &'a [u8] {
    loop {
        read_exact(buf);

        if let Some(packet) = to_packet(buf) {
            return packet;
        }
    }
}

fn read_exact(_: &mut [u8]) {
    todo!()
}

fn to_packet<'a>(_: &'a [u8]) -> Option<&'a [u8]> {
    todo!()
}

I get the following error:

error[E0502]: cannot borrow `*buf` as mutable because it is also borrowed as immutable
 --> src/lib.rs:3:9
  |
1 | pub fn read_packet<'a>(buf: &'a mut [u8]) -> &'a [u8] {
  |                    -- lifetime `'a` defined here
2 |     loop {
3 |         read_exact(buf);
  |         ^^^^^^^^^^^^^^^ mutable borrow occurs here
4 | 
5 |         if let Some(packet) = to_packet(buf) {
  |                                         --- immutable borrow occurs here
6 |             return packet;
  |                    ------ returning this value requires that `*buf` is borrowed for `'a`

I think it should work because:

  1. The mutable borrow in read_exact completes on line 3.
  2. If to_packet returns Some then the value is returned to the caller.
  3. If not, the immutable borrow of to_packet is over at the end of the loop. So it is free to be taken mutable borrow of in the next iteration.

Can somebody please let me know why this doesn't work?

EDIT:

It seems like a current borrow checker limitation. I tried using Polonius in the nightly and it works fine with

RUSTFLAGS=-Zpolonius cargo +nightly check

Upvotes: 7

Views: 1052

Answers (2)

Jerboas86
Jerboas86

Reputation: 654

pub fn read_packet<'a>(buffer: &'a mut [u8]) -> &'a [u8] {     |
    let buf = buffer;                                          |
    loop {                                                     |
        read_exact(buf);                                       \/
        if let Some(packet) = to_packet(buf) {              'a _
            return packet;                                     |
        }                                                      |
    }                                                          |
}                                                              
                                         
fn read_exact(_: &mut [u8]) {
    todo!()
}

fn to_packet<'b>(_from: &'b [u8]) -> Option<&'b [u8]> {
    todo!()
}

Compile error:

  |
1 | pub fn read_packet<'a>(buffer: &'a mut [u8]) -> &'a [u8] {     
  |                    -- lifetime `'a` defined here
...
4 |         read_exact(buf);                                       
  |         ^^^^^^^^^^^^^^^ mutable borrow occurs here
5 |         if let Some(packet) = to_packet(buf) {              
  |                                         --- immutable borrow occurs here
6 |             return packet;                                   
  |                    ------ returning this value requires that `*buf` is borrowed for `'a`

With Non Lexical Lifetime (NLL):

  • The return statement constrains the packet lifetime to be 'a.
  • If packet is 'a, so buf (to_packet) has to be also 'a.
  • 'a is valid for the entire function. The loop makes the shared reference lifetime conflicts with the exclusive reference lifetime at the next iteration.

The conflict can be reproduced without the loop. This snippet doesn't compile for the same reason that buff is 'a. And we see that the root cause is the conditional return. Again 'a has to be valid for the entire function.

pub fn read_packet<'a>(buf: &'a mut [u8]) -> &'a [u8] {
    if let Some(packet) = to_packet(buf) {    'a _
        return packet;                           |
    }                                            |
    read_exact(buf);                             |
    return &[0];                                \/
}

fn read_exact(_: &mut [u8]) {
    todo!()
}

fn to_packet<'b>(_: &'b [u8]) -> Option<&'b [u8]> {
    todo!()
}

Compile error:

  |
1 | pub fn read_packet<'a>(buf: &'a mut [u8]) -> &'a [u8] {
  |                    -- lifetime `'a` defined here
2 |     if let Some(packet) = to_packet(buf) {
  |                                     --- immutable borrow occurs here
3 |         return packet;
  |                ------ returning this value requires that `*buf` is borrowed for `'a`
4 |     }
5 |     read_exact(buf);
  |     ^^^^^^^^^^^^^^^ mutable borrow occurs here

With NLL, the lifetime is infered like you thought it would be.

pub fn read_packet<'a>(buffer: &'a mut [u8]) -> &'a [u8] {     
    let buf = buffer;                                          
    loop {                                                     
        read_exact(buf);                                       
        if let Some(packet) = to_packet(buf) {              'x _
            return packet;                                     |
        }                                                     \/
    }                                                          
}                                                              
                                         
fn read_exact(_: &mut [u8]) {
    todo!()
}

fn to_packet<'b>(_from: &'b [u8]) -> Option<&'b [u8]> {
    todo!()
}

Upvotes: 1

Netwave
Netwave

Reputation: 42698

It is a compiler limitation atm. You could refactor to something like:

pub fn read_packet<'a>(buf: &'a mut [u8]) {
    loop {
        if read_exact(buf) {
            break;
        }
    }
}


fn is_packet(a: &[u8]) -> bool {
    true
}
fn read_exact<'a>(a: &'a mut [u8]) -> bool {
    is_packet(a)
}

fn to_packet<'a>(_: &'a [u8]) -> Option<&'a [u8]> {
    todo!()
}

fn process_packet<'a>(buf: &'a mut [u8]) {
    read_packet(buf);
    let _packet = to_packet(buf);
}

Playground

Upvotes: 2

Related Questions