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
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With