|
|
|
@ -1,6 +1,6 @@
|
|
|
|
|
# 引用与借用
|
|
|
|
|
|
|
|
|
|
上节中提到,如果仅仅是所有权转移,会让程序变得复杂,那能否像其它编程语言一样,使用某个变量的指针或者引用呢?答案是有的。
|
|
|
|
|
上节中提到,如果仅仅是支持所有权转移,那会让程序变得复杂。 能否像其它编程语言一样,使用某个变量的指针或者引用呢?答案是有的。
|
|
|
|
|
|
|
|
|
|
Rust通过`借用(Borrowing)`这个概念来达成上述的目的: **获取变量的引用,称之为借用(borrowing)**。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来,当使用完毕后,也必须要物归原主.
|
|
|
|
|
|
|
|
|
@ -54,14 +54,13 @@ fn calculate_length(s: &String) -> usize {
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
能注意到两点:
|
|
|
|
|
1. 无需再通过函数参数来传入所有权,通过函数返回来传出所有权,代码更加简洁
|
|
|
|
|
1. 无需像上章一样:先通过函数参数传入所有权,然后再通过函数返回来传出所有权,代码更加简洁
|
|
|
|
|
2. `calculate_length`的参数`s`类型从`String`变为`&String`
|
|
|
|
|
|
|
|
|
|
这里,`&`符号即是引用,它们允许你使用值,但是不获取所有权,如图所示:
|
|
|
|
|
<img alt="&String s pointing at String s1" src="/img/borrowing-01.svg" class="center" />
|
|
|
|
|
<span class="caption">图:`&String s` 指向 `String s1`的示意图</span>
|
|
|
|
|
|
|
|
|
|
`&s1`语法,让我们创建一个**指向s1的引用**,但是并不拥有它。因为并不拥有这个值,当引用离开作用域后,其指向的值也不会被丢弃。
|
|
|
|
|
通过`&s1`语法,我们创建了一个**指向s1的引用**,但是并不拥有它。因为并不拥有这个值,当引用离开作用域后,其指向的值也不会被丢弃。
|
|
|
|
|
|
|
|
|
|
同理,函数`calculate_length`使用`&`来表明参数`s`的类型是一个引用:
|
|
|
|
|
```rust
|
|
|
|
@ -71,7 +70,7 @@ fn calculate_length(s: &String) -> usize { // s 是对 String 的引用
|
|
|
|
|
// 所以什么也不会发生
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
人总是贪心的,可以摸女孩手了,就想着摸摸胳膊(读者中的老司机表示,这个流程完全不对),因此光借用已经满足不了我们了,如果尝试修改借用的变量呢?
|
|
|
|
|
人总是贪心的,可以拉女孩小手了,就想着抱抱柔软的身子(读者中的某老司机表示,这个流程完全不对),因此光借用已经满足不了我们了,如果尝试修改借用的变量呢?
|
|
|
|
|
```rust
|
|
|
|
|
fn main() {
|
|
|
|
|
let s = String::from("hello");
|
|
|
|
@ -84,7 +83,7 @@ fn change(some_string: &String) {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
很不幸,胳膊你没摸到, 哦口误,你修改错了:
|
|
|
|
|
很不幸,妹子你没抱到, 哦口误,你修改错了:
|
|
|
|
|
```console
|
|
|
|
|
error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
|
|
|
|
|
--> src/main.rs:8:5
|
|
|
|
@ -118,7 +117,7 @@ fn change(some_string: &mut String) {
|
|
|
|
|
|
|
|
|
|
##### 可变引用同时只能存在一个
|
|
|
|
|
|
|
|
|
|
不过可变引用并不是随心所欲、想用就用的,它有一个很大的限制:同一作用域,特定数据只能由一个可变引用:
|
|
|
|
|
不过可变引用并不是随心所欲、想用就用的,它有一个很大的限制:同一作用域,特定数据只能有一个可变引用:
|
|
|
|
|
```rust
|
|
|
|
|
let mut s = String::from("hello");
|
|
|
|
|
|
|
|
|
@ -143,6 +142,7 @@ error[E0499]: cannot borrow `s` as mutable more than once at a time 同一时间
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
这段代码出错的原因在于,第一个可变借用`r1`必须要持续到最后一次使用的位置`println!`,在`r1`创建和最后一次使用之间,我们又尝试创建第二个引用`r2`。
|
|
|
|
|
|
|
|
|
|
对于新手来说,这个特性绝对是一大拦路虎,也是新人们谈之色变的编译器`borrow checker`特性之一,不过各行各业都一样,限制往往是出于安全的考虑,Rust也一样。
|
|
|
|
|
|
|
|
|
|
这种限制的好处就是使Rust在编译期就避免数据竞争,数据竞争可由以下行为造成:
|
|
|
|
@ -150,9 +150,9 @@ error[E0499]: cannot borrow `s` as mutable more than once at a time 同一时间
|
|
|
|
|
- 至少有一个指针被用来写入数据
|
|
|
|
|
- 没有同步数据访问的机制
|
|
|
|
|
|
|
|
|
|
数据竞争会导致未定义行为,难以在运行时追踪,并且难以诊断和修复;Rust 避免了这种情况的发生,因为它甚至不会编译存在数据竞争的代码!
|
|
|
|
|
数据竞争会导致未定义行为,难以在运行时追踪,并且难以诊断和修复。而Rust避免了这种情况的发生,因为它甚至不会编译存在数据竞争的代码!
|
|
|
|
|
|
|
|
|
|
很多时候,大括号可以帮我们解决一些问题,通过手动限制变量的作用域:
|
|
|
|
|
很多时候,大括号可以帮我们解决一些编译不通过的问题,通过手动限制变量的作用域:
|
|
|
|
|
```rust
|
|
|
|
|
let mut s = String::from("hello");
|
|
|
|
|
|
|
|
|
@ -178,7 +178,7 @@ println!("{}, {}, and {}", r1, r2, r3);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
错误如下:
|
|
|
|
|
```rust
|
|
|
|
|
```console
|
|
|
|
|
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable 无法借用可变`s`因为它已经被借用了不可变
|
|
|
|
|
--> src/main.rs:6:14
|
|
|
|
|
|
|
|
|
|
@ -194,7 +194,7 @@ error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immuta
|
|
|
|
|
|
|
|
|
|
其实这个也很好理解,借用了不可变的用户,肯定不希望他借用的东西,被另外一个人莫名其妙改变了。多个不可变借用被允许是因为没有人会去试图修改数据,然后导致别人的数据被污染。
|
|
|
|
|
|
|
|
|
|
> 注意,引用的作用域从创建开始,一直持续到它最后一次使用的地方,这个跟变量的作用域有所不同,变量的作用域从创建持续到某一个花括号`}`
|
|
|
|
|
> 注意,引用的作用域s从创建开始,一直持续到它最后一次使用的地方,这个跟变量的作用域有所不同,变量的作用域从创建持续到某一个花括号`}`
|
|
|
|
|
|
|
|
|
|
Rust的编译器一直在优化,早期的时候,引用的作用域跟变量作用域是一致的,这对日常使用带来了很大的困扰,你必须非常小心的去安排可变、不可变变量的借用,免得无法通过编译,例如以下代码:
|
|
|
|
|
```rust
|
|
|
|
@ -225,11 +225,9 @@ fn main() {
|
|
|
|
|
|
|
|
|
|
所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂状态:当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。
|
|
|
|
|
|
|
|
|
|
让我们尝试创建一个悬垂引用,Rust 会通过一个编译时错误来避免:
|
|
|
|
|
让我们尝试创建一个悬垂引用,Rust会抛出一个编译时错误:
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名: src/main.rs</span>
|
|
|
|
|
|
|
|
|
|
```rust,ignore,does_not_compile
|
|
|
|
|
```rust
|
|
|
|
|
fn main() {
|
|
|
|
|
let reference_to_nothing = dangle();
|
|
|
|
|
}
|
|
|
|
@ -265,10 +263,10 @@ this function's return type contains a borrowed value, but there is no value for
|
|
|
|
|
该函数返回了一个借用的值,但是已经找不到它所借用值的来源
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
让我们仔细看看我们的 `dangle` 代码的每一步到底发生了什么:
|
|
|
|
|
仔细看看 `dangle` 代码的每一步到底发生了什么:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```rust,ignore,does_not_compile
|
|
|
|
|
```rust
|
|
|
|
|
fn dangle() -> &String { // dangle 返回一个字符串的引用
|
|
|
|
|
|
|
|
|
|
let s = String::from("hello"); // s 是一个新字符串
|
|
|
|
@ -278,9 +276,9 @@ fn dangle() -> &String { // dangle 返回一个字符串的引用
|
|
|
|
|
// 危险!
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
因为 `s` 是在 `dangle` 函数内创建的,当 `dangle` 的代码执行完毕后,`s` 将被释放。不过我们尝试返回它的引用。这意味着这个引用会指向一个无效的 `String`,这可不对!Rust 不会允许我们这么做。
|
|
|
|
|
因为 `s` 是在 `dangle` 函数内创建的,当 `dangle` 的代码执行完毕后,`s` 将被释放, 但是此时我们又尝试去返回它的引用。这意味着这个引用会指向一个无效的 `String`,这可不对!
|
|
|
|
|
|
|
|
|
|
这里的解决方法是直接返回 `String`:
|
|
|
|
|
其中一个很好的解决方法是直接返回 `String`:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
fn no_dangle() -> String {
|
|
|
|
@ -290,7 +288,7 @@ fn no_dangle() -> String {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
这样就没有任何错误了。所有权被移动出去,所以没有值被释放。
|
|
|
|
|
这样就没有任何错误了,最终`String`的**所有权被转移给外面的调用者**。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 借用规则总结
|
|
|
|
|