Rust所有权
什么是所有权
所有权是Rust最独特的特性,它让Rust无需GC就可以保证内存安全。Rust的核心特性就是所有权。
为什么需要所有权
要说清楚为什么需要所有权,就需要明白所有权你用来做什么的 -------内存管理;
所有程序在运行时都必须管理它们使用计算机内存的方式。
常见的内存管理方式
- 有些语言有垃圾收集机制,在程序运行时,它们会不断地寻找不再使用的内存。例如:Java、Go…
- 在其他语言中,程序员必须显示的分配和释放内存。C/C++
Rust所有权如何进行内存管理?
- 内存是通过一个所有权系统来管理的,其中包含一组编译器在编译时检查的规则;
- 对于某个值来说,当拥有它的变量走出作用范围时,内存会立即自动的交还给操作系统。 drop()
为什么要用所有权进行内存管理?
Rust为什么不选用上面的两种内存管理方式呢?
因为Rust定位是速度惊人且内存利用率极高的安全编程语言。使用垃圾收集器虽然能保证安全,但性能方面做不到极致,而程序员手动管理内存又存在很大的安全隐患。 所有权就很好的满足了上面两点。
所有权如何使用
1. 变量和数据
多个变量与同一个值的交互方式会根据值保存在stack还是heap上而有所不同
- 值保存在stack上,下一个变量会复制上一个变量的值;
fn main() {
let x = 6;
let y = x;
println!("x:{}, y:{}.", x, y);
}
- 值保存在heap中,赋值动作会将所有权移动到下一个值上
fn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("s1: {}, s2: {}.", s1, s2);
}
- 当s1赋给s2,String的数据被复制了一份
- 在stack上复制了一份指针、长度、容量
- 并没有复制指针所指向的heap上的数据
- 当变量离开作用域时,Rust会自动调用drop函数,并将变量使用的heap内存释放
- 当s1、s2离开作用域时,他们都会尝试释放相同的内存,此时会导致二次释放。
- 为了保证内存安全:
- Rust没有尝试复制被分配的内存
- Rust让s1失效,当s1离开作用域的时候,Rust不需要释放任何东西
- 也许会将复制指针、长度、容量视为浅拷贝,但由于Rust让s1失效了,所以我们用一个新的术语:移动(Move)
- 隐含一个设计原则:Rust不会自动创建数据的深拷贝。就运行时性能而言,任何自动赋值的操作都是廉价的
2.函数
在语义上,将值传递给函数和把值赋给变量是类似的:将值传递给函数将发生移动或复制
3.返回值
- 函数在返回值的过程中同样也会发生所有权的转移
- 当一个包含heap数据的变量离开作用域时,他的值就会被drop函数清理,除非数据的所有权移动到另一个变量上
如何使用某值而不获得其所有权
例如:
获得一个字符串的长度,你可能会用以下方式获取
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)
}
此时使用Rust的另一个特性“引用(Reference)”也许会更合适。
引用和借用
- 引用的参数类型是&String而不是String
- &符号就表示引用:允许你引用某些值而不取得其所有权
- 我们把引用作为函数参数这个行为叫做借用
- 我们是否可以修改借用的东西呢?默认情况下是不行的
- 和变量一样,引用默认也是不可变的。加个mut就变为可变借用 &mut
可变借用
fn main(){
let mut s = String::from("Hello");
let s1 = &mut s;
let s2 = &mut s;
println!("The length of '{}' is {}.", s1, s2);
}
- 可变引用有一个重要的限制:在特定作用域内,对某一块数据,只能有一个可变的引用
- 这样做的好处是可以在编译时防止数据竞争
- 下列三种行为下会发生数据竞争:
- 两个或多个指针同时访问同一个数据
- 至少有一个指针用于写入数据
- 没有使用任何机制来同步对数据的访问
- 可以通过创建新的作用域,来允许非同时的创建多个可变引用
- 不可以同时拥有一个可变引用和一个不可变的引用
fn main(){
let mut s = String::from("Hello");
{
let s2 = &mut s;
}
let s1 = &mut s;
}
注意:Rust不允许出现悬垂引用(Dangling Reference)
- 悬垂饮用(Dangling Reference): 一个指针引用了内存中的某个地址,而这块内存可能已经释放并分配给其他人使用了
- 在Rust里,编译器可保证引用永远都不是悬垂引用:
- 如果你引用了某些数据,编译器将保证在引用离开作用域之前数据不会离开作用域
下面是错误示范:
fn main(){
let r = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
引用的规则
- 在任何给定的时刻,只能满足下列条件之一:
- 一个可变引用
- 任意数量的不可变的引用
- 引用必须一直有效
结束语
所有权作为Rust的核心之一,需要深刻的去理解它的设计思想。会对往后的Rust编程有很大帮助。
笔者也只刚刚开始学习Rust,不对的地方还请慷慨指出。也希望您可以留言交流心得~