Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does ownership and borrowing work when making slices?

Tags:

rust

Here are two code snippets, but they show a different behavior and I couldn't understand what's going on in there. Their main function is identical. If the borrowing and ownership concept applies to one code (i.e to code 2) why not to another code i.e (code 1)?

code 1:

This code compiles with no errors and prompt the result.

fn main() {
    let mut s = String::from("Hello world");
    let result = first_word(&s);
    s.clear();
    println!("result:{:#?}", result);
}

fn first_word(s: &String) -> usize {
    let s = s.as_bytes();
    //println!("{:?}",s);
    for (i, &item) in s.iter().enumerate() {
        if item == 32 {
            return i;
        }
    }
    s.len()
}

Code 1 Output :

Finished dev [unoptimized + debuginfo] target(s) in 0.28s
 Running `target/debug/rust_Slices`
 result:5

Code 2: This code won't compile and gives an error.

fn main() {
    let mut s = String::from("Hello world");
    let result = first_word(&s);
    s.clear();
    println!("{:#?}", result);
}

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

Code 2 Output:

cannot borrow `s` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:4
  |
3 |    let result = first_word(&s);
  |                            -- immutable borrow occurs here
4 |    s.clear();
  |    ^^^^^^^^^ mutable borrow occurs here
5 |     println!("{:#?}",result);
  |                      ------ immutable borrow later used here
like image 624
Muhammad Areeb Siddiqui Avatar asked Sep 19 '25 20:09

Muhammad Areeb Siddiqui


2 Answers

Let's decompose:

// Let's build a string, which basically is a growable array of chars
let mut s = String::from("Hello world");

// now make result a slice over that string, that is a reference
// to a part of the underlying chars
let result = first_word(&s);

// now let's remove the content of the string (which of course removes
// what the slice was referring to)
s.clear();

// and let's... print it ?
println!("{:#?}", result);

Hopefully the borrow checker prevents you from doing this with this exact error:

cannot borrow s as mutable because it is also borrowed as immutable

And if you've understood this, the solution should be obvious: don't make result a window over another string but a string by itself, having its own content: change the second line to

let result = first_word(&s).to_string();

Now you can clear the source string and keep the first word. Of course to_string() isn't a costless operation so you might want to try keep the source string around in real applications.

like image 77
Denys Séguret Avatar answered Sep 23 '25 12:09

Denys Séguret


The key thing here is lifetimes. By default lifetimes argument for functions with one input reference and output reference are the same (liftime elision). So compiler implicitly changes the code following way:

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

That means that the result borrows the input argument. You can explicitly make lifetimes different and eliminate error in the main but in this case first_word will not compile:

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

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
 --> src/main.rs:7:21
  |
7 |             return &s[0..i];
  |                     ^^^^^^^
  |
note: first, the lifetime cannot outlive the lifetime 'a as defined on the function body
like image 42
Zefick Avatar answered Sep 23 '25 10:09

Zefick