Dowson Jones
Dowson Jones

Reputation: 55

Why does using ? on Result<_, &str> cause a lifetime error?

I have this code that doesn't compile:

impl BytePacketBuffer {
    fn read(&mut self) -> Result<u8, &str> {
        if self.pos >= 512 {
            return Err("End of buffer".into());
        }
        let res = self.buf[self.pos];
        self.pos += 1;

        Ok(res)
    }

    fn read_u16(&mut self) -> Result<u16, &str> {
        let res = ((self.read()? as u16) << 8) | (self.read()? as u16);
        Ok(res)
    }
}
error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src/byte_packet_buffer.rs:53:51
   |
52 |     fn read_u16(&mut self) -> Result<u16, &str> {
   |                 - let's call the lifetime of this reference `'1`
53 |         let res = ((self.read()? as u16) << 8) | (self.read()? as u16);
   |                     ------------                  ^^^^^^^^^^^ second mutable borrow occurs here
   |                     |
   |                     first mutable borrow occurs here
   |                     returning this value requires that `*self` is borrowed for `'1`

But if I modify the return type from &str to String, it compiles without error. Can anybody explain why I get an error when returning &str but not when returning String?

Upvotes: 3

Views: 421

Answers (2)

Chayim Friedman
Chayim Friedman

Reputation: 70910

@cafce25 explained well how lifetime elision leads to incorrect lifetime, but this still does not explain why this fails. Even with the incorrect lifetimes, when we borrow self for the &str it is the error case, and the error case is immediately returning from the function and thus does not borrow self further.

Well, this is because the ? operators desugars roughly into (simplified, but the actual desugaring doesn't matter here):

match expr {
    Ok(v) => v,
    Err(e) => return Err(From::from(e)),
}

And it is well known that the borrow checker is overly conservative w.r.t. branches. It extends borrow in return expression to the rest of the function, causing the &str to live even when it cannot really live.

If you compile it with Polonius (cargo +nightly rustc -- -Zpolonius), it compiles successfully.

Upvotes: 3

cafce25
cafce25

Reputation: 27258

Because you did not specify any lifetimes Rust will infer them according to the elision rules to be this:

fn read<'a>(&'a mut self) -> Result<u8, &'a str>

which means it will infer self to be borrowed for as long as you keep the Result or anything it contained around. To fix it you could just specify the lifetime of the returned str because it's always static anyways.

fn read(&mut self) -> Result<u8, &'static str>

Upvotes: 4

Related Questions