正式开端
Rust内存管理
- 大部分堆内存的需求在于动态巨细,小部分需求是更长的生命周期
- Rust默认将堆内存的生命周期和运用它的栈内存的生命周期绑在一起,并留了个小口儿 leaked 机制,让堆内存在需要的时分,能够有超出帧存活期的生命周期
值的创立
- 编译时能够确认巨细的值都会放在栈上,包括 Rust 供给的原生类型比方字符、数组、元组(tuple)等,以及开发者自界说的固定巨细的结构体(struct)、枚举(enum) 等
- 假如数据结构的巨细无法确认,或者它的巨细确认但是在运用时需要更长的生命周期,就最好放在堆上
struct
-
Rust 在内存中排布数据时,会依据每个域的对齐(aligment)对数据进行重排,使其内存巨细和拜访功率最好
-
C 言语会对结构体对齐的规则
a. 首先确认每个域的长度和对齐长度,原始类型的对齐长度和类型的长度一致
b. 每个域的起始位置要和其对齐长度对齐,假如无法对齐,则添加 padding 直至对齐
c. 结构体的对齐巨细和其最大域的对齐巨细相同,而结构体的长度则四舍五入到其对齐的倍数
Rust 编译器默以为开发者优化结构体的摆放,但你也能够 运用#[repr] 宏,强制让 Rust 编译器不做优化 ,和 C 的行为一致,这样,Rust 代码能够方便地和 C 代码无缝交互
enum
- 在 Rust 下它是一个标签联合体(tagged union),它的巨细是标签的巨细加上最大类型的长度
- 依据方才说的三条对齐规则,tag 后的内存,会依据其对齐巨细进行对齐。一般而言,64 位 CPU 下,enum 的最大长度是:最大类型的长度 + 8
以下代码能够打印常见数据结构的巨细
use std::collections::HashMap;
use std::mem::size_of;
enum E {
A(f64),
B(HashMap<String, String>),
C(Result<Vec<u8>, String>),
}
// 这是一个声明宏,它会打印各种数据结构本身的巨细,在 Option 中的巨细,以及在 Result 中的巨细
macro_rules! show_size {
(header) => {
println!(
"{:<24} {:>4} {} {}",
"Type", "T", "Option<T>", "Result<T, io::Error>"
);
println!("{}", "-".repeat(64));
};
($t:ty) => {
println!(
"{:<24} {:4} {:8} {:12}",
stringify!($t),
size_of::<$t>(),
size_of::<Option<$t>>(),
size_of::<Result<$t, std::io::Error>>(),
)
};
}
fn main() {
show_size!(header);
show_size!(u8);
show_size!(f64);
show_size!(&u8);
show_size!(Box<u8>);
show_size!(&[u8]);
show_size!(String);
show_size!(Vec<u8>);
show_size!(HashMap<String, String>);
show_size!(E);
}
/*
Type T Option<T> Result<T, io::Error>
----------------------------------------------------------------
u8 1 2 16
f64 8 16 16
&u8 8 8 16
Box<u8> 8 8 16
&[u8] 16 16 24
String 24 24 32
Vec<u8> 24 24 32
HashMap<String, String> 48 48 56
E 56 56 64
*/
Option 合作带有引用类型的数据结构,比方 &u8、Box、Vec、HashMap ,没有额定占用空间
Option 复用了引用类型的第一个域(是个指针),当其为 0 时表明 None
Option<&u8>中,将指针作为tag运用,指针为0时,表明没有数据,也即None; 不然,表明有数据,则是Some
vec<T> 和 String
-
Vec<T>结构是 3 个 word 的胖指针
a. 指向堆内存的指针 pointer
b. 分配的堆内存的容量 capacity
c. 数据在堆内存的长度 length
-
很多动态巨细的数据结构,在创立时都有相似的内存布局:栈内存放的胖指针,指向堆内存分配出来的数据
值的运用
- 对 Rust 而言,一个值假如没有完成 Copy,在赋值、传参以及函数回来时会被 Move
- Copy 和 Move 在内部完成上,都是浅层的按位做内存仿制,只不过 Copy 答应你拜访之前的变量,而 Move 不答应
- 无论是 Copy 还是 Move,它的功率都是非常高的
- 一般咱们主张在栈上不要放大数组
- 动态数组数据添加会导致内存运用率低下,运用 shrink_to_fit 节约内存
值的毁掉
当一个值要被开释,它的 Drop trait 会被调用
简略类型的开释
- 变量 greeting 是一个字符串,在退出作用域时,其 drop() 函数被主动调用
- 开释堆上包括 “hello world” 的内存
- 然后再开释栈上的内存
复杂数据结构的开释
- 比方一个结构体,那么这个结构体在调用 drop() 时,会顺次调用每一个域的 drop() 函数
- 假如域又是一个复杂的结构或者集合类型,就会递归下去,直到每一个域都开释洁净
- student 变量是一个结构体,有 name、age、scores
- 其中 name 是 String,scores 是 HashMap,它们本身需要额定 drop()
- 又由于 HashMap 的 key 是 String,所以还需要进一步调用这些 key 的 drop()
- 整个开释顺序从内到外是:先开释 HashMap 下的 key,然后开释 HashMap 堆上的表结构,最终开释栈上的内存
堆内存开释
所有权机制规定了,一个值只能有一个所有者,所以在开释堆内存的时分,便是单纯调用 Drop trait
开释其他资源
Rust 对所有的资源都有很好的 RAII 支撑。
小结
好用链接
- 数据对齐
- String 源码
- Vec<T>结构 源码
- Rust cheats 快速手册
- RAII
- RFC
- 生命周期概念 帖子
精选问答
-
Result<String, ()> 占用多少内存?为什么?
a. 引用类型的第一个域是个指针,而指针是不可能等于 0 的,咱们能够复用这个指针:当其为 0 时,表明 None,不然是 Some
b. 关于 Result<String, ()> 也是如此,String 第一个域是指针,而指针不能为空,所以当它为空的时分,正好能够表述 Err(())
-
rust 中的 feature 是干什么用的,怎样开发?
a. feature 用作条件编译,你能够依据需要选择运用库的某些 feature。它的优点是能够让编译出的二进制比较灵活,依据需要装入不同的功用
b. 界说 feature,你能够看 cargo book:doc.rust-lang.org/cargo/refer…
c. 以下是个简略例子
在 cargo.toml 中,能够界说: [features] filter = ["futures-util"] // 界说 filter feature,它有额定对 futures-util 的依靠。 [dependencies] futures-util = { version = "0.3", optional = true } // 这个 dep 声明成 optional 在 lib.rs 中: #[cfg(feature = "filter")] pub mod filter_all; // 只有编译 feature filter 时,才引进 mod feature_all 编译