共计 2654 个字符,预计需要花费 7 分钟才能阅读完成。
1. 援用
很多场景中,咱们可能只是想读取某个变量指向的值,并不想取得其所有权,这个时候就能够应用 援用。其实在很多其余的编程语言中,也有援用的概念。
- 简略来讲,援用是创立一个变量,指向另个指针的地址,而不是间接指向 该指针指向的堆内存地址
- 通过 & 取地址符获取对一个指针变量的援用
例如在 Rust 中,咱们这样创立援用:
let s1 = String::from("hello");
// 获取 s1 的援用
let s = &s1;
-
下图就很好的示意了 援用 的关系
- 变量 s1 是栈内存中的一个指针地址,通过 ptr 记录了存储于堆内存中的 String(“hello”) 的地址
- 变量 s 也存在栈内存中,通过 ptr 记录了 s1 的指针地址,来实现对 String(“hello”) 的援用
2. 借用
应用 援用 作为函数参数的行为被称作 借用,如何应用 借用 来躲避某个变量的所有权产生挪动,咱们能够看以下例子:
fn main() {let s = String::from("hello!");
// 应用 s 的援用作为入参,s 的所有权就不会产生挪动
let len = get_length(&s);
println!("the length of {} is {}", s, len); // the length of hello! is 6
}
fn get_length(string: &String) -> usize {
// 援用和变量一样,默认也是不可变的
// string.push_str("world"); // `string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
string.len()}
3. 可变援用
从下面的例子咱们能够晓得,援用和变量一样,默认也是不可变的,咱们不能批改援用所指向的值。但如果想要这么做,那么能够应用 mut 关键字,将援用转为可变的:
fn main() {let mut s = String::from("hello!");
/*
* 应用 mut 关键字,将 s 的可变援用作为入参,* 这样 s 的所有权既不会产生挪动,函数中也能通过 可变援用 来批改 s 的值
*/
let len = get_length(&mut s);
// 在 get_length 函数中咱们实现了对 s 的批改
println!("the length of {} is {}", s, len); // the length of hello!world is 11
}
fn get_length(string: &mut String) -> usize {
// 通过可变援用对值进行批改
string.push_str("world");
string.len()}
3.1 可变援用的重要限度
- 对应一个指针变量,在一个特定的作用域内,只能有一个可变援用。起因也很好了解,如果在一块作用域内,当一个变量存在两个可变援用,那就意味着同一时间可能有两个变量管制着同一块内存空间,就会产生数据竞争,很容易在运行时产生 bug,因而 Rust 通过编译时查看,来躲避这样的问题呈现
3.1.1 同一作用域,只能存在一个可变援用
通过例子能够看到,当咱们对 变量 origin 进行了两次可变援用,编译时就间接报错
fn main() {let mut origin = String::from("hello");
let ref1 = &mut origin;
let ref2 = &mut origin; // error: cannot borrow `origin` as mutable more than once at a time
println!("{}, {}", ref1, ref2);
}
3.1.2 不可变援用能够有多个
如果是同时应用多个不可变援用,则不会有这个限度,因为不可变援用其实就是只读的,不存在可能的内存平安危险。通过这个例子咱们能够看到,Rust 是容许这样应用的:
fn main() {let origin = String::from("hello");
let ref1 = &origin;
let ref2 = &origin;
println!("{}, {}", ref1, ref2);
}
3.1.3 某些场景下,能够存在多个可变援用
从下面的两个例子咱们曾经晓得,如果一个作用域内同时存在两个以上的可变援用,那么就可能产生数据竞争,那么是否存在某些场景,会呈现多个可变援用呢?咱们看上面这个例子:
-
其实在程序执行过程中,只有走出作用域,那么作用域中的变量就会被开释,这个例子中当申明 ref2 时,ref1 曾经被销毁了,所以还是能够保障可变援用的第一条准则
fn main() {let mut origin = String::from("hello"); { let ref1 = &mut origin; println!("{}", ref1); } // 当 ref2 申明时,ref1 曾经被销毁了 let ref2 = &mut origin; println!("{}", ref2); }
3.1.4 不能同时领有一个可变援用和一个不可变援用
对于一个指针变量,它的可变援用和不可变援用其实是互斥的,不能同时存在。起因很简略,可变援用能够批改指向内存空间的值,当值被批改,不可变援用的意义也就不存在了。因而 Rust 在编译时会进行查看,发现这种状况则会间接报错:
fn main() {let mut origin = String::from("hello");
let ref1 = &origin;
let ref2 = &mut origin;// cannot borrow `origin` as mutable because it is also borrowed as immutable
println!("{} {}", ref1, ref2);
}
4. 悬垂援用
这一种呈现在 C/C++ 等语言中的 bug 场景,形容的是这样一种场景,一个变量所指向的内存空间曾经被开释或调配给其余程序应用,但这个变量任然无效。在 Rust 中,编译器会查看这种场景,来避免出现悬垂援用。假如编译通过,上面就是一个会产生悬垂援用的场景:
fn main() {let s = String::from("haha");
// s 的所有权被挪动到 helper 的作用域中
let a = helper(s);
/*
* 假如编译通过:* 当 helper 调用结束,s 指向的内存空间就被开释了,但在 helper 中返回了 s 的援用,* 其实这个援用曾经生效,这时 变量 a 就变为了一个 悬垂援用
*/
}
fn helper(s: String) -> &String {println!("{}", s);
&s // error: missing lifetime specifier
}
但其实在编译时,Rust 就不会容许 helper 返回 s 的援用