2

I'm new to Rust and I'm reading the The Rust Programming Language online book. I'm now stuck on a problem about the Rust's borrow check, the code example is shown below:

fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();
    for (i, &b) in bytes.iter().enumerate() {
        if b == b' ' {
            return &s[..i];
        }
    }

    &s[..]
}

fn main() {
    let mut s = String::from("hello world");

    let word = first_word(&s);
    s.clear();

    println!("word = {}", word);
}

Rust compiler complains about the code with the following error message:

error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
  --> src/main.rs:16:5
   |
15 |     let word = first_word(&s);
   |                           -- immutable borrow occurs here
16 |     s.clear();
   |     ^^^^^^^^^ mutable borrow occurs here
17 | 
18 |     println!("word = {}", word);
   |                           ---- immutable borrow later used here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0502`.
error: Could not compile `demo`.

To learn more, run the command again with --verbose.

The online book explains that when executing s.clear(), a new mutable reference to s is created and conflicted with the existing immutable reference word because word is not out of its scope until the last println! statement. It seems like that somehow, Rust's borrow checker figured out that word, which is returned by the first_word function, refers to s. How does it achieve that?

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
Lancern
  • 403
  • 2
  • 9
  • @DenysSéguret: I guess the book was not updated. – Matthieu M. Jul 25 '19 at 14:26
  • This is explained in detail in the subsection [Validating References with Lifetimes](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html) of the book. I suggest skipping forward to that section to get an understanding of how lifetimes in Rust work. – Sven Marnach Jul 25 '19 at 14:29
  • Thanks for your response. I just tried to modify the code to return string literals directly from the `first_word` function and the complain stills. It seems like that the borrow checker makes its decisions on the lifetime you just mentioned. I'll read about it, thanks. – Lancern Jul 25 '19 at 14:42
  • 1
    @Lancern The borrow checker only looks at the function prototype (i.e. the argument and return types) to determine the relationship between input and output references. Changing the implementation of the function does not change anything about how the function is treated at the call site. – Sven Marnach Jul 25 '19 at 14:44
  • In order to return a reference you have to relate that reference to one of the inputs. Then the caller can be responsible for providing an input that lives long enough to use the returned reference. You simply cannot return an arbitrary reference; in order to return something unconnected to an input you would have to return an (owned) item not a reference. – Erik Eidt Jul 25 '19 at 15:33

1 Answers1

2

When you have

fn first_word(s: &String) -> &str {

it really means

fn first_word<'a>(s: &'a String) -> &'a str {

(see lifetime elision).

That is, the lifetime of the output is the one of the input.

That's how the borrow checker can deduce that word, which is returned by the first_word function, refers to s.

trent
  • 25,033
  • 7
  • 51
  • 90
Denys Séguret
  • 372,613
  • 87
  • 782
  • 758