Skip to main content

引用和借用

如何让函数使用某个值,但不获得其所有权?


fn main() {
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);

println!("The length of {} is {}", s2, len);
}

fn calculate_length(s: String) -> (String, usize) {
let length = s.len();
(s, length)
}

  • 参数的类型是 &String 不是 String
  • &符号就表示引用:允许你引用某些值而不取得其所有权

fn main() {
let s1 = String::from("Hello");
/* 调用 s1 的时候并未取得它的所有权,只是把s1作为参数的引用传到了 caculate_length 里面 & 符号代表的就是引用,它只是引用s1并不拥有s1,当它走出
作用域的时候它指向的值并不会被清理掉。
*/
let len = caculate_length(&s1);
println!("The length of {} is {}", s1, len);
}

fn caculate_length(s: &String) -> usize {
s.len()
}
/**
结束的时候 s 就出了作用域,但是它并未具有它所指向字符串的所有权,所以它指向的那个值并不会被清理,所以跟其他的函数参数一样,s的作用域还是这么大。
不同的是,它不会在离开自己作用域的时候销毁其指向的数据,因为它并不拥有该数据的所有权。
所以当一个函数使用引用作为它的参数而不是真实的值作为它的参数的时候,我们就不会为了归还所有权而把这个值返回回去,因为在这个时候我们就没有获得其所有权。
*/

  • 图中的 s 就是 s1 的引用,ptr 实际上就是个指针指向了 s1,s1 实际上也是个指针,指向了存在 Heap 上面真实的内容,从内存上讲引用就是这样的。
  • C++里面解引用是 ,在 rust 里面也是

把引用作为函数参数的这个行为叫做借用

  • 是否可以修改借用的东西?

    fn main() {
    let s1 = String::from("Hello");
    let len = caculate_length(&s1);
    println!("The length of {} is {}", s1, len);
    }

    fn caculate_length(s: &String) -> usize {
    s.push_str(" World");
    s.len()
    }

    // 错误提示
    Compiling lesson5 v0.1.0 (D:\Rust\lesson5)
    error[E0596]: cannot borrow `*s` as mutable, as it is behind a `&` reference
    --> src\main.rs:8:5
    |
    7 | fn caculate_length(s: &String) -> usize {
    | ------- help: consider changing this to be a mutable reference: `&mut String`
    8 | s.push_str(" World");
    | ^^^^^^^^^^^^^^^^^^^^ `s` is a `&` reference, so the data it refers to cannot be borrowed as mutable

    For more information about this error, try `rustc --explain E0596`.
    error: could not compile `lesson5` due to previous error
  • 和变量一样,引用默认也是不可变的。

可变引用


fn main() {
let mut s1 = String::from("Hello");
let len = caculate_length(&mut s1);
println!("The length of {} is {}", s1, len);
}

fn caculate_length(s: &mut String) -> usize {
s.push_str(" World");
s.len()
}

注意
  • 可变引用有个重要的限制:在特定的区域内,对某一块数据,只能有一个可变的引用。

fn main() {
let mut s = String::from("Hello");

let s1 = &mut s;
let s2 = &mut s;

println!("The length of {} is {}", s1, s2);
}

// 错误提示
Compiling lesson5 v0.1.0 (D:\Rust\lesson5)
error[E0499]: cannot borrow `s` as mutable more than once at a time
--> src\main.rs:5:14
|
4 | let s1 = &mut s;
| ------ first mutable borrow occurs here
5 | let s2 = &mut s;
| ^^^^^^ second mutable borrow occurs here
6 |
7 | println!("The length of {} is {}", s1, s2);
| -- first borrow later used here

For more information about this error, try `rustc --explain E0499`.
error: could not compile `lesson5` due to previous error

好处:防止在编译的时候防止数据竞争

  • 数据竞争触发的 3 个条件:
    1. 两个或多个指针同时访问同一个数据。
    2. 至少有一个指针用于写入数据。
    3. 没有使用任何机制来同步对数据的访问。

如果这 3 个条件都满足就可能会发生数据竞争,这种数据竞争在运行时是很难发现的,所以说 Rust 做了个根本的解决,就是在编译的时候就防止了这种数据竞争。 如果满足这 3 种情况,那么在编译的时候就会报错。

  • 可以通过创建新的作用域,来允许非同时的创建多个可变引用。

fn main() {
let mut s = String::from("Hello");
let r1 = &s;
let r2 = &s;

let s1 = &mut s;
println!("{},{},{}", r1, r2, s1);
}


另外的限制

  • 不可以同时拥有一个可变引用和一个不可变的引用
  • 多个不变的引用书可以的

fn main() {
let mut s = String::from("Hello");
{
let s1 = &mut s;
}
let s2 = &mut s;
}
// 错误提示
Compiling lesson5 v0.1.0 (D:\Rust\lesson5)
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src\main.rs:6:14
|
3 | let r1 = &s;
| -- immutable borrow occurs here
...
6 | let s1 = &mut s;
| ^^^^^^ mutable borrow occurs here
7 | println!("{},{},{}", r1, r2, s1);
| -- immutable borrow later used here

For more information about this error, try `rustc --explain E0502`.
error: could not compile `lesson5` due to previous error

悬空引用 Dangling References

一个指针引用了内存中的某个地址,而这块内存可能已经释放并分配给其他人使用了。

  • Rust 里编译器可保证引用永远都不是悬空引用:
    • 如果你引用了某写数据,编译器将保证在引用离开作用域之前数据不会离开作用域。
missing lifetime specifier
fn danger() -> &String { // 没有参数 却返回了 String 类型的引用
let s = String::from("hello world");
&s
}
// 如果能编译通过 s 值在这个作用域结束之后,也就是说出了作用域之后就会被销毁了,而对它的这个引用却被返回了而继续有效,这个引用将会被指向一个已经被释放的内存地址,这就是悬空指针。
// 所以上边的函数肯定编译不会通过

// 错误提示
Compiling lesson5 v0.1.0 (D:\Rust\lesson5)
error[E0106]: missing lifetime specifier
--> src\main.rs:5:16
|
5 | fn danger() -> &String {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
|
5 | fn danger() -> &'static String {
| +++++++

For more information about this error, try `rustc --explain E0106`.
error: could not compile `lesson5` due to previous error

引用的规则

  • 引用的规则只能满足下列条件之一:
    • 一个可变的引用
    • 任意数量不可变的引用
  • 引用必须一直有效