理解思路
1、所有权的存在是为了解决内存安全的问题,而产生的一种新的区别于其它语言与内存打交道(垃圾回收机制和手动管理内存)的方法--通过所有权来管理内存。
2、那么如何通过所有权管理内存呢?rust就规定每个值只能有一个所有者,且离开了作用域以后就会被drop.
有了所有者,就存在管理权转移的可能,那么就有3、4描述的问题
3、无需在堆上分配空间的栈上数据,可以通过自动拷贝,不用发生所有权转移 ,比如整型数据这类基本数据类型
rust
//例如
let x :i32=5;
let y = x; //重新分配了内存空间绑定到了y
println!("x :{},y: {}",x , y); //调用x不会产生错误
4、但是 String
不是基本类型,而且是存储在堆上的,因此不能自动拷贝。就会发生所有权转移
rust
let s1 = String::from("hello");
let s2 = s1; // s1 失效,不再指向任何数据,s2拥有了s1的所有权
println!("{}, world!", s1); // 产生错误
5、正因为有了以上规则,所以rust规定在将值传递给函数,一样会发生 移动
或者 复制
rust
fn main() {
let s = String::from("hello"); // s 进入作用域
takes_ownership(s); // s 的值移动到函数里 ...
// ... 所以到这里不再有效
let x = 5; // x 进入作用域
makes_copy(x); // x 应该移动函数里,
// 但 i32 是 Copy 的,所以在后面可继续使用 x
} // 这里, x 先移出了作用域,然后是 s。但因为 s 的值已被移走,
// 所以不会有特殊操作
fn takes_ownership(some_string: String) { // some_string 进入作用域
println!("{}", some_string);
} // 这里,some_string 移出作用域并调用 `drop` 方法。占用的内存被释放
fn makes_copy(some_integer: i32) { // some_integer 进入作用域
println!("{}", some_integer);
} // 这里,some_integer 移出作用域。不会有特殊操作
6、那么通过5点,就会产生一个新问题?s 如果要在takes_ownership()后使用怎么办?
rust
1、要么在函数前copy 一下: let s1 = s.clone(),这样就会重新分配内存保存一份同样的数据让s1拥有所有权,s的操作就与s1无关。
2、或者函数返回s的值,调用函数后重新接收。
7、所以为了避免变量在调用函数时发生所有权转移,Rust 通过 借用(Borrowing)
这个概念来达成目的,获取变量的引用,称之为借用(borrowing)
ini
& 符号即是引用,它们允许你使用值,但是不获取所有权
* 符号是解出引用所指向的值(也就是**解引用**)
例如 let x = 5 ; y = &x; //这里的&x就是引用了x的值,*y解引用y所指向的x的值
就可以避免通过函数参数传入所有权,然后再通过函数返回来传出所有权
8、由于存在不可变 和 可变变量,所以引用也是存在 不可变引用 和 可变引用
rust
fn main() {
let s = String::from("hello");
change(&s);
}
fn change(some_string: &String) {
some_string.push_str(", world");
}
//想改变s的值,这里就会报错,因为 s 是不可变变量,所以引用以后也不能改变其值
因此 s 需要被定义为 let mut s = String::from("hello"); 可变变量才行,然后change的引用才是可变引用;同时change方法也要发生改变
rust
//同时change方法也要发生改变
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
不过可变引用并不是随心所欲、想用就用的,它有一个很大的限制: **同一作用域,特定数据只能有一个可变引用**
9、有一个神奇的地方,可变变量,可以使用不可变引用,但是可变引用与不可变引用不能同时存在
rust
let mut s = String::from("hello");
let r1 = &s; // 没问题
let r2 = &s; // 没问题
let r3 = &mut s; // 不行,会产生错误
println!("{}, {}, and {}", r1, r2, r3); //这里不会像其它语言一样,r1、r2输出和r3可能改变以后的值,只会报错。
10、悬垂引用(Dangling References)
悬垂引用也叫做悬垂指针,意思为指针指向某个值后,这个值被释放掉了,而指针仍然存在,其指向的内存可能不存在任何值或已被其它变量重新使用。在 Rust 中编译器可以确保引用永远也不会变成悬垂状态:当你获取数据的引用后,编译器可以确保数据不会在引用结束前被释放,要想释放数据,必须先停止其引用的使用。
rust
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s // 返回字符串 s 的引用
}// 这里 s 离开作用域并被丢弃。其内存被释放。所以不能返回指针 ,解决方法直接返回 s ,将s的所有权转移给外部调用者使用