rust学习六 引用与借用

Posted by rpl on May 15, 2020

通过此链接查看rust学习系列的其他文章

一、引用

rust使用 与符号 & 表示引用。允许我们使用一个变量的值,而避免该变量所有值的转移。

1
2
3
4
5
6
7
8
9
10
11
fn main() {
    let s1 = String::from("hello");
    
    let len = calculate_len(&s1);
    
    println("The length of '{}' is {}", s1, len);
}

fn calculate_len(s: &String) -> usize {
    s.len()
}

我们将使用引用作为函数参数的行为称为借用。借用很形象的表达函数所使用的参数的所有权是不属于函数本身的,函数只是暂时借用了此变量的值。 当超出函数作用域后,引用失效。该值不会被销毁,因为该值得所有者仍然存在。


二、不可变引用与可变引用

如果一个声明的引用是不可变的,则不允许改变该引用所指向的值。如果引用是可变的,且该引用指向的值是可变类型,则该可变引用允许改变它指向的值。

1
2
3
4
5
6
7
8
9
10
fn main() {
    let mut s = String::from("hello");  // s is mutable
    
    change(&mut s);
} 

fn change(some_string: &mut String) {
    some_string.push_str(", world");  // some_string is Mutable reference
    								  // so it can change the value of s
}

三、可变引用的限制

对于特定范围内的特定数据块,只能有一个可变引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fn main() {
    let mut s = String::from("hello");
    
    let r1 = &mut s;  
    let r2 = &mut s;  
    
    println!("r1: {}, r2: {}", r1, r2);  // 在一个范围内同时使用了两个可变引用
}


error[E0499]: cannot borrow `s` as mutable more than once at a time
 --> src/main.rs:4:14
  |
3 |     let r1 = &mut s;
  |              ------ first mutable borrow occurs here
4 |     let r2 = &mut s;
  |              ^^^^^^ second mutable borrow occurs here
5 | 
6 |     println!("r1: {}, r2: {}", r1, r2);
  |                                -- first borrow later used here

error: aborting due to previous error

如下面代码,分别在各自的作用域内使用是可以的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fn main() {
    let mut s = String::from("hello");
    
    let r1 = &mut s;
    println!("r1: {}",  r1);
    r1.push_str(", world");

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

}

// r1: hello
// r2: hello, world

rust对可变引用的限制避免数据竞争。数据竞争会导致程序出现未知的行为,且在运行时难以追踪定位、诊断和修复bug。以下三种行为发生时就会发生数据竞争:

  1. 2个或多个指针同时访问同一数据。
  2. 其中至少一个指针当时正在写入数据。
  3. 没有使用任何的数据访问同步机制。

同样,结合可变与不可变引用,以下代码也会出现错误:

1
2
3
4
5
let mut s = String::from("hello");

let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // big problem

因为在同一作用域内,同一变量的可变引用,会导致无法预料不可变引用指向的值是否会突然改变。rust在编译时会阻止这种行为。但是扎起同一作用域内的多个不可变引用是被允许的,因为它们是不可变的,不会引起数据竞争。


四、 悬空引用

垂悬引用是指一个指针引用的内存位置可能已经给了其他指针。在有指针的编程语言中,通过释放一些内存,同时保留指向该内存的指针,很容易错误的创建一个悬空指针。

相反,在Rust中,编译器保证引用永远不会是悬空引用:如果你有一个对某些数据的引用,编译器将确保数据不会在数据引用超出作用域之前超出作用域。

尝试创建一个悬空指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> & String {
    let s = String::from("hello");  // s is valid
    &s  // &s is valid
    
}  // s go out of scope, s will be dealloacted.


error[E0106]: missing lifetime specifier
 --> src/main.rs:5:16
  |
5 | fn dangle() -> & 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 dangle() -> &'static  String {
  |                ^^^^^^^^

error: aborting due to previous error

错误说明是这个函数的返回类型包含了一个借用值,但是却无值可借用。引用要保证始终具有有效性。

解决方法是直接返回s:

1
2
3
4
5
6
7
8
9
10
fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> & String {
    let s = String::from("hello");  // s is valid
    s  // s is valid
    
}  // s go out of scope, but ownership is moved out, and nothing deallocated


五、引用的规则

通过以上对于引用的学习和理解,总结如下:

  1. 在任何给定的时间,你可以有以下任意一个,但不能同时有两个:一个可变引用或任意数量的不可变引用
  2. 引用必须始终有效

六、words, statements and translation

  1. ampersands 与符号 &

  2. Likewise, the signature of the function uses & to indicate that the type of the parameter s is a reference.

    同样,函数的签名使用&表示形参的类型是引用。

    signature

  3. At any given time, you can have either but not both of the following: one mutable reference or any number of immutable references

    在任何给定的时间,你可以有以下任意一个,但不能同时有两个:一个可变引用或任意数量的不可变引用

  4. you can have only one mutable reference to a particular piece of data in a particular scope

    对于特定范围内的特定数据块,只能有一个可变引用

  5. This restriction allows for mutation but in a very controlled fashion

    这种限制允许引用的可变性,但以一种非常受控的方式

    fashion 方式, 时尚

  6. In languages with pointers, it’s easy to erroneously create a dangling pointer, a pointer that references a location in memory that may have been given to someone else, by freeing some memory while preserving a pointer to that memory.

    在有指针的编程语言中,通过释放一些内存,同时保留指向该内存的指针,很容易错误地创建一个悬空指针,该指针引用的内存位置可能已经给了其他指针。

    In languages with pointers, 在有指针的编程语言中

    dangling pointer 悬空指针

    erroneously 错误地

  7. if you have a reference to some data, the com- piler will ensure that the data will not go out of scope before the reference to the data does.

    如果你有一个对某些数据的引用,编译器将确保数据不会在数据引用超出作用域之前超出作用域。