所有权
所有权是rust最独特的特性,它让Rust无需GC就可以保证内存安全。
通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。如果违反了任何这些规则,程序都不能编译。在运行时,所有权系统的任何功能都不会减慢程序。
入栈比在堆上分配内存要快,因为(入栈时)分配器无需为存储新数据去搜索内存空间;其位置总是在栈顶。相比之下,在堆上分配内存则需要更多的工作,这是因为分配器必须首先找到一块足够存放数据的内存空间,并接着做一些记录为下一次分配做准备。
访问堆上的数据比访问栈上的数据慢,因为必须通过指针来访问。现代处理器在内存中跳转越少就越快(缓存)。
关于各种数据类型的内存模型的讨论我们以后会再讨论
- 所有权存在的原因:管理heap(堆)数据
1. 跟踪代码的哪些部分正在使用heap的那些数据
2. 最小化heap上的重复数据量
3. 清理heap上未使用的数据以避免空间不足
- 所有权三条规则
1. 每个值都有一个变量,这个变量是该值的所有者
2. 每个值同时只能有一个所有者
3. 当所有者超出作用域时,该值将被删除
- 以String类型为例
在heap上分配,能够存储在编译时未知数量的文本
fn main(){
let mut s= String::from("Hello");
s.push_str(",World");
println!("{}",s);
}
Rust释放堆的方式为:对于某个值来说,当拥有它的变量走出作用范围时,内存会立即自动的交还给操作系统
如本例中,s作用域结束失效实际会调用drop函数
- 变量和数据交互的方式:移动(Move)
let s1=String::from("hello");
let s2=s1;
String 由三部分组成,如图左侧所示:一个指向存放字符串内容内存的指针,一个长度,和一个容量。这一组数据存储在栈上。右侧则是堆上存放内容的内存部分。
当把s1赋给s2,String的数据被复制了一份:
在stack上复制了一份指针、长度、容量
并没有复制指针指向的heap上的数据
当变量离开作用域时,Rust会自动调用drop函数,并将变量使用的heap内存释放。
为了保证内存安全:
Rust没有尝试复制被分配的内存
Rust让s1失效
当s1离开作用域的时候,Rust不需要释放任何东西
浅拷贝
也许会将复制指针、长度、容量视为浅拷贝,但由于Rust让s1失效了,所以叫移动。这种行为避免了二次释放的可能性。Rust不会自动化创建数据的深拷贝。
- 克隆(Clone)
如果想对heap上的String数据进行深度拷贝,可以使用clone方法
- 复制(Copy trait)
可以用于像整数这样完全存放在stack上的类型。
如果一个类型实现Copy,那么旧的变量在赋值后仍然是可用的。
所有权与函数
在语义上,将值传递给函数和把值赋给变量是类似的:发生移动或复制
fn main(){
let s = String::from("Hello World");
take_ownership(s);
let x=5;
make_copy(x);
println!(x:{
},x);
}
s在进入函数后被移动到函数里,不再有效
x由于是i32类型实现了copy trait,往函数里传的是x的副本,x在第8行以后仍然是有效的。
到main末尾,s和x离开了作用域。
- 返回值与作用域
所有权也会发生改变
fn main() {
let s1 = gives_ownership(); // gives_ownership 将返回值
// 转移给 s1
let s2 = String::from("hello"); // s2 进入作用域
let s3 = takes_and_gives_back(s2); // s2 被移动到
// takes_and_gives_back 中,
// 它也将返回值移给 s3
} // 这里, s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,
// 所以什么也不会发生。s1 离开作用域并被丢弃
fn gives_ownership() -> String {
// gives_ownership 会将
// 返回值移动给
// 调用它的函数
let some_string = String::from("yours"); // some_string 进入作用域.
some_string // 返回 some_string
// 并移出给调用的函数
//
}
// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) -> String {
// a_string 进入作用域
//
a_string // 返回 a_string 并移出给调用的函数
}
- 如何让函数使用某个值,但不获得其所有权?
Rust有一个特性叫引用
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
引用:允许你使用值但不获取其所有权
s是s1的引用,s即是一个指针指向s1。
s离开函数作用域后,不会销毁数据,因为没有所有权
-
借用
把引用作为函数参数的行为
借用的变量不可变 -
可变引用
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
可变引用有一个很大的限制:如果你有一个对该变量的可变引用,你就不能再创建对该变量的引用。这些尝试创建两个 s 的可变引用的代码会失败。在编译时防止数据竞争。
可以通过创建新的作用域,来允许费同时的创建多个可变引用。
let mut s = String::from("hello");
{
let r1 = &mut s;
} // r1 在这里离开了作用域,所以我们完全可以创建一个新的引用
let r2 = &mut s;
另外一个限制:
不可以同时拥有一个可变引用和一个不变引用
可以多个不可变
(不可变引用 r1 和 r2 的作用域在 println! 最后一次使用之后结束,这也是创建可变引用 r3 的地方。它们的作用域没有重叠,所以代码是可以编译的。编译器在作用域结束之前判断不再使用的引用的能力被称为 非词法作用域生命周期(Non-Lexical Lifetimes,简称 NLL)。)
- 悬垂引用
rust编译器会报错,防止其发生
参考:《Rust程序设计语言》