Appearance
✍读书笔记:『Rusts程序设计语言』ch04 - 02
之前在Rust所有权中讨论过 Rust 中使用函数参数传递实际上是移动行为。如果我们不想将值传入到函数中,这个时候需要使用引用。
引用
引用(reference)的的方法是 &val,& 运算符很像 C 语言的取地址运算符,实际它们的逻辑上也部分相似。
rust
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}这段代码里,传入函数的不再是 s1 了,而是 s1 的引用 &s1 ,这样,当 &s1 传入函数后,s1依然可以被访问,因为引用不会改变所有权。

上面这张图是引用的实际形式,先不用管 String 对象的属性,起码我们可以看出来,s 是类似于 C 语言里面的二级指针,但它的使用限制比二级指针多(后面会说)。
借用
创建一个引用的行为称作借用(borrowing)。顾名思义,因为是借用来的值,所以变量不会具有值的所有权,同时也不能对值进行修改(这点就和 C 语言二级指针区别开来了)。
rust
fn main() {
let s = String::from("hello");
change(&s);
}
fn change(some_string: &String) {
some_string.push_str(", world");
}上面这段代码编译报错,因为这里对引用变量进行了修改。
可变引用
因为借用不能修改,所以有可变引用(mutable reference)。
rust
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}把 &String 类型换成了 &mut String 类型。这样子,函数调用一来不会取得值的所有权,二来又可以对原变量的值进行修改。
那么可变引用是不是和 C 里面二级指针等价了?也不尽然,它还有一个限制:如果你对一个变量进行可变引用,就不能再在同一作用域创建该变量的引用(包括可变引用)。
rust
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &s;
println!("{}, {}", r1, r2);即使顺序反一下,也是不行的:
rust
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
let r3 = &mut s;
println!("{}, {}, and {}", r1, r2, r3);可以通过创建一个新的作用域来解决:
rust
let mut s = String::from("hello");
{ let r1 = &mut s; }
let r2 = &mut s;但是要注意 r1 的作用域范围,在大括号外部是没法取得所要的值的。
这些种种限制,主要是为了避免『数据竞争』(data race)。
垂悬引用
这块的概念和 C 一样,如果引用指向的空间已经被释放,那么指针指向数据实际是未知的。
rust
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}上面的 s 这个变量,实际在 dangle 函数结束的时候就已经离开了作用域,被 rust 给 drop 了,相当于 C 里面使用 free(3) 释放了内存,而返回的指针是指向这个内存的地址。幸运的是,rust 的编译器会帮你发现这个错误,并在编译的时候报错。
而 C 语言则完全信任程序员,这种代码会成功编译通过,并且能够运行。悬置指针是 C 语言里最令人头疼的问题,悬置指针指向的地址保存的内容可能会因为运行情况的改变而不同,有时候不会出错,有时候产生的错误又莫名其妙。想要定位到出错的这个指针的位置也是难上难。这里就体现出使用 Rust 的意义所在了。