在 Rust 书中,他们给出了以下示例
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s);
s.clear();
println!("the first word is: {}",word)
}
其中
first_word
定义为:
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[..]
}
编译器错误是
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:18:5
|
16 | let word = first_word(&s);
| -- immutable borrow occurs here
17 |
18 | s.clear(); // error!
| ^^^^^^^^^ mutable borrow occurs here
19 |
20 | println!("the first word is: {}", word);
| ---- immutable borrow later used here
For more information about this error, try `rustc --explain E0502`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error
根据我对所有权的理解,
&s
引用了s
,然后当它超出范围时被删除。当 first_word
函数返回时就会发生这种情况。然后我们得到一个新的引用,它专门指向内存中受切片影响的第一个字符,称为 word
。据我了解, word
和 s
本质上都指向为字符串分配的相同内存块,但可以完全指向不同的地址。然而,当 s.clear()
被调用时(我假设)隐式地采用了对 s
的可变引用,称为 &self
。 &self
指向 s
但 word
指向其他东西。我的问题是,所有权规则是如何被违反的。据我了解,可变引用和不可变引用在同一范围内不能指向同一事物,但在这种情况下,不可变引用词和可变引用&self
不应该指向同一事物?以图表形式我有
您缺少的是概念性的:当您使用一个引用派生另一个引用时,派生引用的生命周期不能比派生它的引用更长。这在函数签名中最为明显。
例如,让我们创建一个将引用作为输入并返回引用的函数:
fn foo(s: &str) -> &str
由于生命周期省略,这意味着相同的事情:
fn foo<'a>(s: &'a str) -> &'a str
酷。现在让我们实现它,但是我们不会从输入中导出返回的引用:
fn foo(_: &str) -> &str {
"foo"
}
现在让我们使用它,给它一个从
String
派生的字符串切片的引用,清除 String
,然后打印返回的切片:
fn main() {
let mut s = "bar".to_owned();
let y = foo(&s);
s.clear();
println!("{y}");
}
此操作失败并出现相同的错误:
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:9:5
|
7 | let y = foo(&s);
| -- immutable borrow occurs here
8 |
9 | s.clear();
| ^^^^^^^^^ mutable borrow occurs here
10 | println!("{y}");
| --- immutable borrow later used here
这样做的目的是为了说明函数的主体是完全不相关的。我们已经告诉编译器,返回的引用源自参数
s
中包含的引用。
引用是如何得出的或者它指向什么与此分析完全无关。
因此,示例代码中的内容是对字符串切片 (
word
) 的引用,该字符串切片源自 String
(s
)。然后,您尝试通过调用 s
(此方法需要 s.clear()
)隐式获取对 &mut self
的独占引用,然后使用 word
。这是违规行为: word
借用了 s
,即使它与 &s
并不指向同一事物,并且您试图同时专门借用 s
。
事实上,这个特定的示例准确地说明了为什么别名规则对于 Rust 如何实现内存安全是不可或缺的,即使在单线程代码中也是如此。如果允许编译此代码(例如,用 C++ 重写),那么在
word
之后访问 s.clear()
的内容将是一个经典的释放后使用错误。
这与您无法获取对
Vec
的元素的引用然后清除 Vec
的原因相同 - 因为对元素的引用源自对 Vec
的引用,对该元素借用了 Vec
。