所有权系统和引用在Rust中是至关重要的,它的设计使得rust的GC机制与众不同,也让rust获得了性能上的回报。
所有权
- 所有权:一个数值只能被一个变量所拥有,即该变量唯一拥有该数值,若变量离开了其作用域,数值也会被释放掉。
- 如果将一个变量赋值给另一个变量,则意味着对应的值的所有权会被移交出去,那么原先的变量将不再拥有值的所有权,这也是为什么在rust中,变量的赋值也叫变量的绑定。
// 例子
fn main(){
let s1 = String::from("Hello");
let s2 = s1; // 所有权移交 -> 即move
println!("{}", s1); // 报错
}
- 可以看到,上述例子的s1将所有权移交给s2后,自己便不再拥有对字符串
Hello
的控制。
从内存的视角观察
- 还是上述例子,在内存里表示出来,如下图所示。
可以看出,再进行变量绑定时,s2实际上是s1的浅拷贝结果,但唯一不同的是指针的移交操作,即指针不是单纯的拷贝,而是从s1上“移交”给了s2,这也是一个move的过程。
如果不想让s1的所有权移交出去,则可以调用String的clone方法来实现深拷贝 ,在堆内存上再开辟一个空间,这样两个变量都有对应的值,这样就不会发生变量无法使用的情况了。
let s1 = String::from("hello");
let s2 = s1.clone(); // 直接重新开辟一个空间,尽量少用,会影响rust性能
println!("s1 = {}, s2 = {}", s1, s2);
- 需要注意的是,这并不能说明存储在堆内存中的数据只能有一个引用(即指针),这里只强调了move这个操作,即所有权的移交过程,而没有说明堆数据只能有一个引用。
Copy类型
- 之所以提到copy类型是因为它与上述的String类型有所不同,具有Copy属性的数据类型的数据都是直接存储于栈内存中的,即具有copy特征的数据在进行赋值时,旧变量依然可以使用。
fn main(){
let a = 1;
let b = a; // 将 a 赋值给 b
}
当赋值时,进行了浅拷贝操作,由于简单类型直接在栈上进行浅拷贝,开销小,两个
1
时存储在不同的栈内存空间里,所以两个变量都有与之对应的数值,所以不会像刚才的String
类型一样,发生s1
不可使用的情况。下面是一些具有Copy属性的类型:
- 整数类型
- 布尔类型
- 浮点数类型
- 字符类型
- 元祖
- 不可变引用
&T
好处: 避免内存二次释放以及开发上不必要的困扰
- Rust垃圾回收比较特殊,它是在每个作用域结束时会调用一次drop函数来清理对内存。拿上述的String的例子来说,两个String变量指向同一个位置,如果没有所有权的限制,那么s1和s2离开作用域时,gc会对同一块堆内存进行二次释放,这样会导致内存的泄漏问题。
- 所以一旦有了所有权的移交,Rust认为s1已经不再拥有对数据的所有权,所以不会对s1处理任何东西,而只对s2的堆内存进行释放处理。
- 同时,所有权还能保证开发时产生不必要的困扰,假如有多个变量对同一块内存进行引用,那么当其中一个变量一不小心对内存进行修改时,其产生的副作用就会波及到所有与之相关的变量上,造成令人匪夷所思的结果。显然,有了所有权的限制,我们不会有上述的担忧。