Rust学习笔记之内存管理
Rust学习笔记之内存管理
Rust语言以其独特的内存安全机制而闻名,其中最核心的就是所有权(ownership)系统。本文将深入探讨Rust的内存管理机制,包括变量的所有权、移动语义、引用以及生命周期管理等关键概念。通过具体的代码示例和内存分配图,帮助读者理解Rust如何在不使用垃圾回收器的情况下实现内存安全。
1. Ownership机制
变量内存排布及回收原则
Rust的内存管理遵循"用户决定每个变量的生命周期,Rust回收变量的内存以及其他相关资源"这一最高原则。在Rust中,每个值都只有一个单一的所有者,只有这个所有者才能决定该值的生命周期。当所有者被销毁时,其拥有的值也会随之被销毁。
让我们通过几个常见的变量生命周期示例来分析:
- vector容器类型的内存分配
fn print_padovan() {
let mut padovan = vec![1, 1, 1]; // allocated here
for i in 3..10 {
let next = padovan[i - 3] + padovan[i - 2];
padovan.push(next);
}
print!("P(1..10)= {:?}", padovan); // dropped here
}
在这个例子中,padovan
的内存分配如下:vec
类型的变量分配在栈上,其存储的内容分配在堆上。栈上分配的内容包括:1. 指针,2. vec
的容量大小,3. vec
当前存储的元素长度。
- Box类型变量的内存分配
普通简单变量直接分配在栈上,而通过Box
封装的变量则存储在堆上:
{
let point = Box::new((0.625, 0.5)); // point allocated here
let label = format!("{:?}", point); // label allocated here
assert_eq!(label, "(0.625, 0.5)"); // both dropped here
}
- Rust的复杂引用关系形成的ownership树
struct Person {
name: String,
birth: i32,
}
let mut composers = Vec::new();
composers.push(Person {
name: "Palestrina".to_string(),
birth: 1525,
});
composers.push(Person {
name: "Downland".to_string(),
birth: 1563,
});
composers.push(Person {
name: "Lully".to_string(),
birth: 1632,
});
for composer in &composers {
println!("{}, born {}", composer.name, composer.birth);
}
在这个例子中,变量composers
指向一个vector
,vector
中的元素是一个结构体,它指向具体结构体的内存结构(参考String
类型内存结构和i32
类型存储结构)。
Rust的ownership机制总结:
- 你可以将一个变量从一个所有者转移到另一个所有者;
- 标准库中提供了
Rc
和Arc
,这些允许某些值同时拥有多个所有者; - 你可以使用引用(borrow a reference),但引用是非拥有指针,具有有限的生命周期。
move - 变量所属变更机制
在Rust中,类似变量赋值、参数传递或函数返回值都不会赋值value,而是采用一种move
的机制。
move
机制:原所有者放弃value的拥有权并转交给destination,并且自身变成未初始化状态;之后destination控制value的生命周期。
对于Copy types
(如整型、浮点型、字符型、布尔类型,以及由这些类型组成的元组和固定长度的数组),采用复制的方式。
我们来看一个简单的赋值的例子:
let s = vec![
"undon".to_string(),
"ramen".to_string(),
"soba".to_string(),
];
let t = s;
let u = s; // error: use of moved value 's'
t = s
执行之前:
t = s
执行之后:
由于使用了move
的机制,当我们直接使用集合内部元素的时候就会发生控制权的转移,因此为了防止集合内部出现未初始化的变量,Rust不允许直接使用集合内部元素进行复制,Rust推荐使用引用的方式进行引用相应变量。
还是之前的例子:
let s = vec![
"udon".to_string(),
"ramen".to_string(),
"soba".to_string(),
];
let t = s[0]; // error[E0507]: cannot move out of borrowed content
let m = &s[0]; // ok
共享ownership - Rc和Arc
Rc
和Arc
:引用计数类型指针,当某些变量需要同时被多个分支使用,且需要在所有使用者使用之后再进行释放的场景。
其中Arc
是线程安全的(Atomic reference count),而Rc
则是非线程安全的。
Rc
的实现原理是其指向的变量的内容在堆上分配,并且堆上还记录了该变量的引用计数,但是Rc
指向的变量必须是不可变的。
let s: Rc<String> = Rc::new("shirataki".to_string());
let k: Rc<String> = s.clone();
let u = s.clone();
2. 引用
引用在Rust中被称为非拥有性(nonowning pointer)指针,这种指针对所指向的对象的生命周期没有影响。
Rust中创建某个指针也称为borrowing the value:what you have borrowed, you must eventually return to its owner.
引用分两种:
- 共享引用:只能读取被引用的对象,当某个对象有共享引用时,即使是该对象的所有者也不可以修改对象。
let t = &T;
- 可变引用:可以修改被引用的对象内容,可变引用和共享引用是互斥的。
let t = &mut T;
对一个引用的集合的遍历,也会导致循环内部的集合元素的的访问变成相应的引用的方式。
通过引用访问原始值
在Rust中通过引用访问原始值,标准语法需要使用*
操作符,但是由于引用在Rust中运用广泛,.
操作符也可以做隐式的变量的原始值的获取。
引用安全性
Rust中使用引用的时候需要保证引用和其所引用的变量的生命周期是否匹配,需要保证引用的生命周期应该在所引用的变量的生命周期范围内。
可以使用生命周期的限制符限制函数参数或者返回值引用的生命周期范围。如下所示,<'a>
用来限制所传入函数f的参数p的生命周期应该是局限在函数内部的,也就是说f中不应该让p赋值给生命周期超过函数的返回的变量。
fn f<'a>(p: &'a i32) {
// <'a> 表示一个f的生命周期参数
}
如下所示限制了函数的返回值引用的生命周期,表示返回值的生命周期和传入参数的生命周期一致。
fn smallest<'a>(v: &'a [i32]) -> &'a i32 {
}
如下是限制结构体的内变量的生命周期,表示结构体内部r引用的变量的生命周期应该和S保持一致。
struct S<'a> {
r: &'a i32,
}
以上就是本文的全部内容,希望对大家的学习有所帮助。