Skip to content

Commit

Permalink
📝 更新rust文章
Browse files Browse the repository at this point in the history
  • Loading branch information
waterbang committed May 16, 2024
1 parent 0397371 commit 3cda9af
Show file tree
Hide file tree
Showing 2 changed files with 308 additions and 0 deletions.
308 changes: 308 additions & 0 deletions source/_posts/2024/rust的特性备忘.md
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.

0 comments on commit 3cda9af

Please sign in to comment.