1. Rust的内存管理模型
1.1 内存管理模型
常见语言的内存管理模型:
-
C/C++: 手动
-
Python/Java/C#: GC自动回收
-
Rust: The Rust Compiler,在编译期间发现问题
-
Stop the World:
是与垃圾回收相关的术语,它指的是在进行垃圾回收时系统暂停程序的运行
这个术语主要用于描述一种全局性的暂停,即所有应用线程都被停止,以便垃圾回收器能够安全地进行工作。这种全局性的停止会导致一些潜在的问题,特别是对于需要低延迟和高性能的应用程序
需要注意的是,并非所有的垃圾回收算法都需要"stop the world", 有一些现代的垃圾回收器采用了一些技术来减小全局停顿的影响, 比如并发垃圾回收和增量垃圾回收
1.2 C/C++ 内存错误
- 内存泄漏(Memory Leaks)
C++
int* ptr = new int;
// 忘记释放内存
// delete ptr;
- 悬空指针(Dangling Pointers)
c++
int* ptr = new int;
delete ptr;
// ptr 现在是悬空指针
- 重复释放(Double Free)
c++
int* ptr = new int;
delete ptr;
delete ptr;
- 数组越界(Array Out of Bounds)
c++
int arr[5]
arr[5] = 10;
- 内存泄漏(Memory Leaks)
c++
int* ptr
*ptr = 10; // 野指针
- 使用已经释放的内存(Use After free)
c++
int* ptr = new int;
delete int;
*ptr = 10;
- 堆栈溢出(Stack Overflow)
递归导致的堆栈溢出
- 不匹配的 new/delete 或 malloc/free
1.3 Rust内存管理模型
-
所有权系统(Ownership System)
-
借用(Borrowing)
- 不可变引用(不可变借用)
- 可变引用(可变借用)
-
生命周期(Lifetimes)
-
引用计数(Reference Counting)
rust
fn get_length(s: String) {
println!("String: {}", s.len());
// 函数结束后的 main::s2 也销毁了
}
fn main() {
// copy
let c1 = 1;
let c2 = c1;
println!("{c1}"); // c1 依然存在
// move
let s1 = String::from("Value");
let s2 = s1;
// println!("{s1}"); // error: s1 的 ownership 已经转移给 s2
let _s3 = s2.clone();
println!("{s2}"); // 深拷贝是可以的
get_length(s2);
// println!("{s2}"); // error: 传入函数之后, s2又无法使用了, 已经被函数借用了 ownership
let back = first_word("hello world");
println!("{}", back);
}
fn dangle() -> String {
"hello".to_owned()
}
// 静态的生命周期
fn dangle_static() -> &'static str {
"static dangle"
}
// String 与 &str vec u8 ref
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
2. String与&str
2.1 定义
- String是一个堆分配的可变字符串类型
rust
pub struct String {
vec: Vec<u8>
}
- &str是指字符串切片引用,是在栈上分配的
- 不可变引用,指向存储在其他地方的 UTF-8 编码的字符串数据
- 由指针和长度构成
2.2 如何选择
注意String是具有所有权的,而 &str 并没有
-
Struct中属性使用String
- 如果不使用显示声明生命周期无法使用 &str
- 不只是麻烦,还有更多的隐患
-
函数参数推荐使用&str (如果不想交出所有权)
- &str 为参数,可以传递 &str 和 &String
- &String 为参数,只能传递 &String,不能传递 &str
rust
struct Person {
name: String,
color: String,
age: i32
}
struct Person2<'a> {
name: &'a str // 字面量的生命周期和结构体的生命周期一致, 标注为a
}
// &String &str
fn print_str(data: &str) {
println!("{}", data);
}
// 只能传 &String
fn print_string(data: &String) {
println!("{}", data);
}
fn main() {
// 字面量转换成String
// 1. String::from("xxx")
// 2. to_string()
// 3. to_owned()
let string_value = String::from("Value C++");
let literal_to_string = "Rust".to_owned();
let new_string_value = string_value.replace("C++", "Cpp");
println!("{string_value} {literal_to_string} {new_string_value}");
// &str
let str_value = "\x52\x75\x73\x74"; // ascii
println!("{str_value}");
let color = "green".to_string();
let name = "John".to_string();
let people = Person {
name: name,
color: color,
age: 89
};
let str_name = "John";
let people2 = Person2 {
name: str_name
};
let string_name = "John".to_string();
print_str(&string_name);
print_str(str_name);
print_string(&string_name);
}
3. 枚举与匹配模式
3.1 枚举
-
枚举(enums)是一种用户自定义的数据类型,用于表示具有一组离散可能值的变量
- 每种可能值都称为"variant" (变体)
- 枚举名::变体名
-
枚举的好处
- 可以使你的代码更严谨、更易读
- More robust programs
-
枚举内嵌类型
ps: 我是写kotlin&&Java的, Rust这个内嵌类型相当于Java/Kotlin中的密封类的职能, 能够携带不同的数据, 并且能够进行模式匹配
rust
enum Shape {
Circle(f64),
Rectangle(f64, f64),
Square(f64)
}
常见的枚举类型: Option 和 Result
rust
pub enum Option<T> {
None,
Some(T)
}
pub enum Result<T, E> {
Ok(T),
Err(E)
}
3.2 匹配模式
- match 关键字实现
- 可以覆盖所有的变体
- 可以用 _、..=、三元(if)等来进行匹配
rust
match number {
0 => println!("Zero"),
1 | 2 => println!("One or Two"),
3..=9 => println!("From three to Nine"),
n if n%2 == 0 => println!("Event number"),
_ => println!("Other")
}
3.3 代码示例
rust
// 最普通的枚举, 没有任何内嵌数据类型
enum Color {
Red,
Yellow,
Blue,
Black
}
enum BuildingLocation {
Number(i32),
Name(String),
Unkonwn
}
impl BuildingLocation {
fn print_location(&self) {
match self {
BuildingLocation::Number(c) => println!("building number: {c}"),
BuildingLocation::Name(str) => println!("building name: {str}"),
_ => println!("unknown")
}
}
}
fn print_color(my_color: Color) {
match my_color {
Color::Red => println!("red"),
Color::Yellow => println!("yellow"),
Color::Blue => println!("blue"),
_ => ()
}
}
fn main() {
print_color(Color::Red);
let buiding = BuildingLocation::Name("Leitorz's house".to_string());
buiding.print_location();
}
4. 结构体、方法、关联函数、关联变量
4.1 结构体简介
结构体是一种用户定义的数据类型,用于创建自定义的数据结构。
rust
struct Point {
x: i32,
y: i32
}
每条数据(x和y)称为属性(字段field),通过点(.)来访问结构体中的属性
4.2 结构体中的方法
这里的方法是指,通过实例调用(&self、&mut self、self)
rust
impl Point {
fn distance(&self, other: &Point) -> f64 {
let dx = (self.x - other.x) as f64;
let dy = (self.y - other.y) as f64;
(dx * dx + dy * dy).sqrt();
}
}
4.3 结构体中的关联函数
关联函数是与类型相关联的函数,调用时为结构体名::函数名
rust
impl Point {
fn new(x: u32, y: u32) -> Self {
Point {x, y}
}
}
4.4 结构体中的关联变量
这里的关联变量是指,和结构体类型相关的变量,也可以在特质或是枚举中
rust
impl Point {
const PI: f64 = 3.14;
}
调用时使用Point::PI
4.5 代码示例
rust
enum Flavor {
Spicy,
Sweet,
Fruity
}
struct Drink {
flavor: Flavor,
price: f64
}
fn print_drink(drink: &Drink) {
match drink.flavor {
Flavor::Spicy => println!("spicy"),
Flavor::Sweet => println!("sweet"),
Flavor::Fruity => println!("fruity")
}
println!("{}", drink.price);
}
impl Drink {
// 关联变量
const MAX_PRICE: f64 = 10.0;
// 不可变借用, 不能改变self的值
fn buy(&self) {
if self.price > Drink::MAX_PRICE {
println!("无法购买");
} else {
println!("购买成功");
}
}
// 关联函数
fn new_fruity_drink(price: f64) -> Self {
Drink {
flavor: Flavor::Fruity,
price: price
}
}
}
fn main() {
let sweet_drink = Drink {
flavor: Flavor::Sweet,
price: 16.0
};
print_drink(&sweet_drink);
sweet_drink.buy();
let fruity_drink = Drink::new_fruity_drink(10.0);
print_drink(&fruity_drink);
}
5. Ownership与结构体
5.1 Ownership Rules
- Each value in Rust has an owner
- There can only be one owner at a time
- Values are automatically dropped when the owner goes out of scope
5.2 Value Passing Semantics 值传递语义
每当将一个值从一个位置传递到另一个位置时,borrow checker 都会重新评估所有权
-
Immutable Borrow 使用不可变的借用,值的所有权仍归发送方所有,接收方直接接收对该值的引用,而不是该值的副本。但是,他们不能使用该引用来修改它指向的值,编译器不允许这样做。释放资源的责任仍由发送方承担。仅当发件人本身超出范围时,才会删除该值。
-
Mutable Borrow 使用可变的借用,所有权和删除值的责任也由发送者承担。但是接收方能够通过它们接收的引用来修改该值。
-
Move这是所有权从一个地点转移到另一个地点。borrow checker 关于释放该值的决定将由该值的接收者(而非发送者)通知。由于所有权已从发送方转移到接收方,因此发送方在将引用移动到另一个上下文后不能再使用该引用,发送方在移动后对 value 的任何使用都会导致错误。
5.3 结构体中关联函数的参数
rust
&self (self: &Self) 不可变引用
&mut self(self: &mut Self) 可变引用
self(self: Self) // Move
impl Point {
get (self: Self) -> i32 {
self.x
}
}
-
self 参数相当于 Point::get(point) 调用后丧失所有权 (Point为实例对象)
-
&self 参数相当于 Point::get(&point)
-
&mut self 参数相当于 Point::get(&mut point)
5.4 代码示例
rust
struct Counter {
number: i32
}
impl Counter {
fn new(number: i32) -> Self {
return Self { number };
}
// 不可变借用
fn get_number(&self) -> i32 {
return self.number;
}
// 可变借用
fn modify_number(&mut self, new_number: i32) {
self.number = new_number;
}
// 释放
fn release(self) {
println!("free {}", self.number);
}
// 合并
fn combine(c1: Self, c2: Self) -> Self {
Self { number: (c1.number + c2.number) }
}
}
fn main() {
let mut counter = Counter::new(10);
let number = counter.get_number();
println!("{number}");
counter.modify_number(12);
let new_number = counter.get_number(); // 可变借用, 改变number后也没有丧失所有权
println!("{new_number}");
counter.release(); // move后无法使用
let c1 = Counter::new(1);
let c2 = Counter::new(2);
let c3 = Counter::combine(c1, c2); // 合并后, c1 和 c2 丧失了所有权
println!("{}", c3.get_number());
}
6. 堆与栈、Copy与More
6.1 堆与栈
Stack:
- 堆栈将按照获取值的顺序存储值,并以相反的顺序删除值
- 操作高效,函数作用域就是在栈上
- 堆栈上存储的所有数据都必须具有已知的固定大小数据
Heap:
- 堆的规律性较差,当你把一些东西放到你请求的堆上时,你请求,请求空间,并返回一个指针,这是该位置的地址
- 长度不确定
6.2 Box
Box是一个智能指针,它提供对堆分配内存的所有权。它允许你将数据存储在堆上而不是栈上,并且在复制或移动时保持对数据的唯一拥有权。使用Box可以避免一些内存管理问题,如悬垂指针和重复释放。
- 所有权转移
- 释放内存
- 解引用
- 构建递归数据结构
rust
struct Point {
x: i32,
y: i32
}
fn main() {
let box_point = Box::new(Point {x: 20, y: 20}); // 将结构体分配在堆上
let mut boxed_point = Box::new(32);
println!("{}", *boxed_point);
*boxed_point += 10;
}
6.3 Copy与Clone
Move: 所有权转移
Clone: 深拷贝
Copy: Copy是在Clone的基础上建立的marker trait(Rust中最类似继承的关系)
-
trait(特质)是一种定义共享行为的机制。Clone也是特质
-
marker trait 是一个没有任何方法的 trait, 它主要用于向编译器传递某些信息, 以改变类型的默认行为
-
stack:
- 基础类型
- tuple和array
- struct与枚举等也是存储在栈上 如果属性有String等在堆上的数据类型会有指向堆的
-
heap:
- Box
- Rc
- String/Vec等
一般来说在栈上的数据类型都默认 copy 但 struct 等默认为 move, 需要 Copy 只需要设置数据类型实现 Copy 特质即可, 或调用 Clone 函数(需要实现 Clone 特质)
rust
// 这里的 `#[derive(...)]` 是一个属性宏。它告诉编译器为 `Book` 这个结构体自动实现括号里的这些 trait
#[derive(Debug, Clone, Copy)]
struct Book {
page: i32,
rating: f64
}
fn main() {
let x = vec![1, 2, 3, 4];
let y = x.clone();
println!("{:?}", y);
println!("{:?}", x);
let x = "ss".to_string();
let y = x.clone();
println!("{x}");
let b1 = Book {
page: 1,
rating: 0.1
};
let b2 = b1; // 默认调用的move
println!("{:?}", b1);
}