-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
308 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,308 @@ | ||
--- | ||
title: rust的特性备忘 | ||
date: 2024-05-16 21:54:54 | ||
tags: rust | ||
index_img: /2024/05/16/rust的特性备忘/title.jpeg | ||
--- | ||
|
||
## 前言 | ||
|
||
由于个人学习的知识学一门忘一门,因此在这里总结一些比较容易忘记的细节内容,以备复习。 | ||
|
||
> 参考 :杨旭<<Rust 编程语言中级教程>> ,《Rust for Rustaceans》 | ||
## 引用 | ||
|
||
rust 提供的引用是指针的一种高级抽象。引用就是指针。 | ||
|
||
- 引用始终引用的是有效数据 | ||
- 引用与 usize 的倍数对齐 | ||
- 引用可以动态大小的类型提供上述保障 | ||
|
||
引用会保证在内存中对齐,没有对齐的部分会填充字节,从而不会影响代码的运行速度。 | ||
|
||
例如:i32 类型在内存中占用的空间为 4 字节。 | ||
|
||
> rust 会把不没有固定长度的类型存储在内部指针的附近,这样可以让程序永远不会超出程序在内存中运行的空间,产生溢出漏洞, | ||
## Raw Pointers(原始指针) | ||
|
||
原始指针是没有 Rust 标准保障的内存地址,即本质上都是需要 unsafe 块包裹的。 | ||
它的速度极快,但是不安全。 | ||
|
||
- 不可变 Raw Pointer: `*const T` | ||
- 可变的 Raw Pointer:`*mutT` | ||
|
||
> 注意: `*constT`,这三个标记放在一起表示的是一个类型 | ||
> | ||
> 例子: `*const String` | ||
`*const T` 和 `*mutT` 之间差异很小,可以互相转换。 | ||
|
||
Rust 的引用(&mutT 和 &T )会编译为原始指针。这意味着无需冒险进入 unsafe 块,就可以获得原始指针的性能。 | ||
|
||
### 例子:把引用转为原始指针 | ||
|
||
```rust | ||
fn main() { | ||
let a:i64 = 42; | ||
let a_ptr: *const i64 = &a as *const i64; | ||
println!("a: {} ({:p})",a,a_ptr); | ||
} | ||
// ouput=> a: 42 (0x7ff7bd530670) | ||
``` | ||
|
||
> {:p} 可以打印指针地址 | ||
解引用 (dereference):通过指针从 RAM 内存提取数据的过程 叫做对指针进行解引用 | ||
|
||
```rust | ||
fn main() { | ||
let a: i64 = 42; | ||
let a_ptr: *const i64 = &a as *const i64; | ||
let a_addr: usize = unsafe { std::mem::transmute(a_ptr) }; | ||
println!("a: {} ({:p}... 0x{:x})", a, a_ptr, a_addr + 7); | ||
} | ||
// output=> a: 42 (0x7ff7bacca370... 0x7ff7bacca377) | ||
``` | ||
|
||
> std::mem::transmute :将一种类型的值的位重新解释为另一种类型。两种类型必须具有相同的大小。如果不能保证这一点,编译将失败。 | ||
### 重要的提示 🔥 | ||
|
||
- 在底层,引用(&T 和 &mutT)被实现为原始指针。但引用带有额外的保障,应该始终作为首选使用 | ||
- 访问 原始指针 的值总是 unsafe 的 | ||
- 原始指针 不拥有值的所有权 | ||
- 在访问时编译器不会检查数据的合法性 | ||
- 允许多个 原始指针 指向同一数据 | ||
- Rust 无法保证共享数据的合法性 | ||
|
||
#### 什么时候可以使用原始指针 | ||
|
||
- 某些 OS 或第三方库需要使用,例如与 C 交互。 | ||
- 共享对某些内容的访问至关重要,运行时性能要求高。 | ||
|
||
## Smart Pointer(智能指针) | ||
|
||
智能指针倾向于包装原始指针,附加更多的能力,不仅仅是对内存地址解引用。 | ||
|
||
### `Box<T>` | ||
|
||
可以把任何东西都放在 Box 里。可接受几乎任何类型的长期存储。新的安全编程时代的主力军。 | ||
|
||
- 优点:将值集中存储在 Heap。 | ||
- 缺点:大小增加。 | ||
|
||
### `Rc<T>` | ||
|
||
`Rc<T>`是 Rust 的能干而吝啬的簿记员。它知道谁借了什么,何时借了什么。 | ||
|
||
- 优点:对值的共享访问。 | ||
- 缺点:大小增加。运行时成本增加,线程不安全。 | ||
|
||
### `Arc<T>` | ||
|
||
`Arc<T>`是 Rust 的大使。它可以跨线程共享值,保证这些值不会相互干扰。 | ||
|
||
- 优点:对值的共享访问,线程安全。 | ||
- 缺点:大小增加,运行时成本增加。 | ||
|
||
### `Cell<T>` | ||
|
||
变态专家,具有改变不可变值的能力。 | ||
|
||
- 优点:内部可变性。 | ||
- 缺点:大小增加,性能降低。 | ||
|
||
### `RefCell<T>` | ||
|
||
对不可变引用执行改变的能力,但有代价。 | ||
|
||
- 优点:内部可变性,可与仅接受不可变引用的 `Rc`、`Arc` 嵌套使用。 | ||
- 缺点:大小增加,运行时成本增加,缺乏编译时保障。 | ||
|
||
### `Cow<T>` | ||
|
||
封闭并提供对借用数据的不可变访问,并在需要修改所有权时延迟克隆数据。 | ||
|
||
- 优点:当只是只读访问时可以避免写入。 | ||
- 缺点:大小可能会增加. | ||
|
||
### `String` | ||
|
||
可处理可变长度的文本,展示了如何构建安全的抽象。 | ||
|
||
- 优点:动态按需增长,在运行时保证正确编码。 | ||
- 缺点:过度分配内存大小。 | ||
|
||
### `Vec<T>` | ||
|
||
程序最常用的存储系统;它在创建和销毁值时保持数据有序。 | ||
|
||
- 优点:动态按需增长。 | ||
- 缺点:过度分配内存大小。 | ||
|
||
### `RawVec<T>` | ||
|
||
是`Vec<T>`和其它动态大小类型的基石,知道如何按需给你的数据提供一个家。 | ||
|
||
- 优点:动态按需增长。与内存分配器一起配合寻找空间。 | ||
- 缺点:代码上一般用不到。 | ||
|
||
### `Unique<T>` | ||
|
||
作为值的唯一所有者,可保证拥有完全控制权。 | ||
|
||
- 优点:需要独占值的类型(如 String)的基础. | ||
- 缺点:代码上一般用不到。 | ||
|
||
### `Shared<T>` | ||
|
||
分享所有权。 | ||
|
||
- 优点:共享所有权,可以将内存与 T 的宽度对齐,即使是空的时候。 | ||
- 缺点:代码上一般用不到。 | ||
|
||
# 内存 | ||
|
||
有 3 个比较重要的内存区域`stack`,`heap`,`static`。 | ||
|
||
- stack 比较快,在内存中比较整齐。 | ||
- heap 比较慢,在内存中比较混乱。 | ||
|
||
## stack 栈内存 | ||
|
||
Stack 是一段内存. 其调用顺序是 LIFO(后进先出)的。 | ||
程序把它作为一个暂存空间,用于函数调用,main 函数就接近 Stack 底部。 | ||
|
||
想把数据放在 Stack,编译器必须知道类型的大小。 | ||
|
||
> 首选使用 Stack,也就是实现了 Sized 的类型。 | ||
### Stack Frame | ||
|
||
Stack Frame 与 rust 的声明周期相关。 | ||
|
||
每个函数都拥有自己的 Frame 当函数返回时,它的 Frame 就被回收了,构成函数本地变量值的那些字节不会立即擦除, 但访问它们也是不安全的。 | ||
|
||
因为它们可能被后续的函数调用所重写(如果后续函数调用的 Frame 与回收的这个有重合的话)。 | ||
|
||
但即使没有被重写,它们也可能包含无法合法使用的值。例如函数返回后被移动的值。 | ||
|
||
### 小技巧 | ||
|
||
当一个函数即需要兼容 `&str`和 `String`的参数。 | ||
|
||
> $str 存在 Stack 上,String 存储在 Heap 上。 | ||
`AsRef<T>` 要求参数实现到 T 这个类型的引用,即使参数不是这样的类型。 | ||
|
||
```rust | ||
fn is_strong<T: AsRef<str>>(password:T) -> bool { | ||
password.as_ref().len() > 6 | ||
} | ||
``` | ||
|
||
`Into<String>` 要求参数转化为 String 这个类型。但是会涉及比较多的步骤。 | ||
|
||
```rust | ||
fn is_strong<T: Into<String>>(password:T) -> bool { | ||
password.as_ref().len() > 6 | ||
} | ||
``` | ||
|
||
## Heap 堆内存 | ||
|
||
Heap 在内存中是混乱的,它是一个内存池,并没有绑定到当前程序的调用栈,是为在编译时没有已知大小的类型准备的。 | ||
|
||
例如: | ||
|
||
- 切片类型 [T] | ||
- 随着程序运行动态改变的 String,Vec<T> | ||
- trait 对象,它允许程序员来模拟一些动态语言的特性:通过允许将多个类型放进一个容器 | ||
- 一些类型的大小不会改变,但是无法告诉编译器需要分配多少内存 | ||
|
||
Heap 允许你显式的分配连续的内存块。当这么做时,你会得到一个指针,它指向内存块开始的地方。 | ||
|
||
Heap 内存中的值会一直有效,直到你对它显式的释放。如果你想让值活得比当前函数 frame 的生命周期还长,就很有用。 | ||
|
||
### Heap 线程安全 | ||
|
||
如果想把值送到另一个线程,当前线程可能根本无法与那个线程共享 stack frames,你就可以把它存放在 heap 上。 | ||
|
||
因为函数返回时 heap 上的分配并不会消失,所以你在一个地方为值分配内存,把指向它的指针传给另一个线程,就可以让那个线程继续安全的操作于这个值。 | ||
|
||
换一种说法:当你分配 heap 内存时,结果指针会有一个无约束的生命周期,你的程序让它活多久都行。 | ||
|
||
### Heap 机制 | ||
|
||
Heap 上面的变量必须通过指针访问。Rust 里与 Heap 交互的首要机制就是 Box 类型。 | ||
|
||
```rust | ||
fn main() { | ||
let a:i32 = 40; // Stack | ||
let b:Box<i32> = Box::new(60); // Heap | ||
let result:i32 = a + *b; // 用指针访问。 | ||
} | ||
``` | ||
|
||
当`Box:new(value)`时,值就会放在 heap 上,返回的`Box<T>`就是指向 heap 上该值的指针。当 box 被丢弃时,内存就被释放。 | ||
|
||
如果忘记释放 heap 内存,就会导致内存泄漏 | ||
有时你就想让内存泄露。 | ||
|
||
例子:在生命周期没有结束的时候手动释放。 | ||
|
||
```rust | ||
fn main() { | ||
let a: Box<i32> = Box::new(1); | ||
let b: Box<i32> = Box::new(1); | ||
let result = *a + *b; | ||
drop(a); | ||
println!("{}", result); | ||
} | ||
``` | ||
|
||
#### 如何让内存泄露 🔥 | ||
|
||
例如有一个只读的配置,整个程序都需要访间它。就可以把它分配在 heap 上。 | ||
|
||
通过 Box:leak 得到一个'static 引用,从而显式的让其进行泄露。 | ||
|
||
### Static 静态内存 | ||
|
||
- static 内存实际是一个统称,它指的是程序编译后的文件中几个密切相关的区域。 | ||
- 当程序执行时,这些区域会自动加载到你程序的内存里。 | ||
- static 内存里的值在你的程序的整个执行期间会一直存活。 | ||
- 程序的 static 内存是包含程序二进制代码的(通常映射为只读的)。 | ||
- 随着程序的执行,它会在本文段的二进制代码中挨个指令进行遍历,而当函数被调用时就进行跳跃。 | ||
- static 内存会持有使用 static 声明的变量的内存,也包括某些常量值, 例如字符串。 | ||
|
||
### ‘static | ||
|
||
'static 是特殊的生命周期,它的名字就是来自于 static 内存区,它将引用标记为只要 static 内存还存在(程序关闭前),那么引用就合法。 | ||
|
||
static 变量的内存在程序开始运行时就分配了,到 static 内存中变量的引用,按定义来说,就是'static 的,因为在程序关闭前它不会被释放。 | ||
|
||
一旦你创建了一个'static 生命周期的引用,就程序的其余部分而言,它所指向的内容都可能在 static 内存中,因为程序想要使用它多久就可以使用多久。 | ||
|
||
#### `T:'static` | ||
|
||
其表示类型 T 可以存活我们想要的任何时长(直到程序关闭),同时这也要求 T 是拥有所有权的和自给自足的。 | ||
|
||
要求这个类型不借用其他的值,要么借用的值也都是`‘static`的。 | ||
|
||
### const 与 static 的区别 | ||
|
||
```rust | ||
const x:i32 = 123; | ||
``` | ||
|
||
- const 关键字会把紧随它的东西声明为常量 | ||
- 常量可在编译时完全计算出来。 | ||
- 在编译期间,任何引用常量的代码会被替换为常量的计算结果值 | ||
- 常量没有内存或关联其它存储(因为它不是一个地方) | ||
- 可以把常量理解为某个特殊值的方便的名称 | ||
|
||
|
||
### 动态内存分配 |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.