内存布局
Rust中的内存布局有点类似Java,只不过不需要JVM控制内存的分配和回收,而是由Rust本身来控制的。 Rust中内存分为堆和栈。
-
栈: 局部变量的连续内存区域
- 值在编译时已经确定了固定的内存大小
- 非常快,只需要移动堆栈指针即可(入栈时分配器无需为存储新数据去搜索内存空间:其位置总是在栈顶)
- 方便管理,遵循函数调用
- 非常好的内存局部性(栈中的数据具有缓存,如果重复访问数据可以通过缓存的方式提高访问性能)
-
堆: 存储函数调用之外的值.
- 运行期间值是动态大小的
- 速度比栈要慢(分配内存时需要在内存空间中查找可以分配的位置)
- 不保证内存局部性(没有缓存,每次访问数据都是在内存中查询内存指针获取到数据)
fn main() {
let s1 = String::from("Hello");
}
以上代码的内存布局
内存管理方法
-
通过手动内存管理完全控制: C, C++, Pascal, …
- 开发者决定什么时候分配、回收内存
- 开发者确定内存指针是否指向了有效的内存.
- 开发者容易操作异常.
-
运行时自动内存管理: Java, Python, Go, Haskell, …
- 运行时系统保障在引用没有结束前不会被释放
- 通过引用技术法、垃圾收集、RAII实现.
Rust的内存管理则是在编译期通过内存校验等操作实现完全控制并且保障安全性。
所有权
所有权是Rust中重要的一个概念。 所有权定义了Rust中每个变量都有确定的一个范围,超出该范围使用变量则被认为是异常。
struct Point(i32, i32);
fn main() {
{
let p = Point(3, 4);//定义变量p,变量p的范围开始
println!("x: {}", p.0);
}//变量p的范围结束点
println!("y: {}", p.1);//此时访问变量p会编译异常
}
移动语义(所有权转移)
移动语义说的是所有权转移,一个变量的所有权是可以转移的,转移之后则原来的变量失效。
fn main() {
let s1: String = String::from("Hello!");//定义变量s1,通过指针指向堆中的内存数据
let s2: String = s1;//s1的所有权转移给s2,即s2的指针指向了Hello内存数据
println!("s2: {s2}");
println!("s1: {s1}");//此时再访问s1则会报错
}
通过下图表示所有权转移:
初始化s1的内存布局:
s1的所有权转移给s2的内存布局:
克隆-Clone
克隆是Rust提供的一个值复制的语法。功能类似Java中的Clone。
#[derive(Default)]
struct Backends {
hostnames: Vec<String>,
weights: Vec<f64>,
}
impl Backends {
fn set_hostnames(&mut self, hostnames: &Vec<String>) {
self.hostnames = hostnames.clone();//克隆hostname的值复制给自己
self.weights = hostnames.iter().map(|_| 1.0).collect();
}
}
Drop
Rust中定义了所有权,超出范围则会自动销毁,如果说要在超出范围时也要执行一些逻辑,则可以通过Drop这个trait来实现。
struct Droppable {
name: &'static str,
}
impl Drop for Droppable { //实现Drop这个trait
fn drop(&mut self) {//重写drop方法,里面实现具体的超出范围时要执行的逻辑
println!("Dropping {}", self.name);
}
}
fn main() {
let a = Droppable { name: "a" };
{
let b = Droppable { name: "b" };
{
let c = Droppable { name: "c" };
let d = Droppable { name: "d" };
println!("Exiting block B");
}//从这里退出该范围时,则会执行变量d和变量c的drop方法
println!("Exiting block A");
}//从这里退出该范围时,则会执行变量b的drop方法
drop(a);//这里执行变量a的drop方法
println!("Exiting main");
}