切片时所有权和借贷如何工作?

时间:2019-07-17 06:41:58

标签: rust

这里有两个代码段,但是它们表现出不同的行为,我不明白那里发生了什么。它们的主要功能是相同的。如果借用和所有权概念适用于一个代码(即代码2),为什么不适用于另一个代码(即代码1)?

代码1:

此代码编译无误,并提示结果。

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()
}

代码1输出:

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

代码2: 该代码将无法编译并给出错误。

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[..]
}

代码2输出:

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

2 个答案:

答案 0 :(得分:2)

让我们分解:

// 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);

希望借用检查器阻止您执行此确切错误:

  

不能借用s可变,因为它也借来不可变

如果您理解了这一点,则解决方案应该很明显:不要使result成为另一个字符串的窗口,而是使一个字符串本身具有自己的内容:将第二行更改为

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

现在,您可以清除源字符串并保留第一个单词。当然,to_string()并非是一项无花费的操作,因此您可能希望尝试在实际应用程序中保留源字符串。

答案 1 :(得分:0)

这里的关键是生命。默认情况下,具有一个输入引用和输出引用的函数的寿命参数相同(liftime elision)。因此,编译器会按照以下方式隐式更改代码:

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[..]
}

这意味着结果借用了输入参数。您可以显着改变生存期并消除main中的错误,但是在这种情况下first_word无法编译:

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