rename 原生指针 to 裸指针

pull/574/head
sunface 3 years ago
parent 612ab8da54
commit 909eaba00c

@ -301,7 +301,7 @@ RAII | 资源获取即初始化(一般不译) |
range | 区间,范围 | range | 区间,范围 |
range expression | 区间表达式 | range expression | 区间表达式 |
raw identifier | 原生标识符 | raw identifier | 原生标识符 |
raw pointer | 原生指针,裸指针 | raw pointer | 裸指针 |
RC | 引用计数 | reference counted RC | 引用计数 | reference counted
reader | 读取器 | reader | 读取器 |
reader/writer | 读写器 | reader/writer | 读写器 |

@ -101,7 +101,7 @@
- [切片和切片引用](advance/confonding/slice.md) - [切片和切片引用](advance/confonding/slice.md)
- [Eq 和 PartialEq](advance/confonding/eq.md) - [Eq 和 PartialEq](advance/confonding/eq.md)
- [String、&str 和 str todo](advance/confonding/string.md) - [String、&str 和 str todo](advance/confonding/string.md)
- [原生指针、引用和智能指针 todo](advance/confonding/pointer.md) - [指针、引用和智能指针 todo](advance/confonding/pointer.md)
- [作用域、生命周期和 NLL todo](advance/confonding/lifetime.md) - [作用域、生命周期和 NLL todo](advance/confonding/lifetime.md)
- [move、Copy 和 Clone todo](advance/confonding/move-copy.md) - [move、Copy 和 Clone todo](advance/confonding/move-copy.md)
@ -215,7 +215,7 @@
- [最终代码](too-many-lists/deque/final-code.md) - [最终代码](too-many-lists/deque/final-code.md)
- [不错的unsafe队列](too-many-lists/unsafe-queue/intro.md) - [不错的unsafe队列](too-many-lists/unsafe-queue/intro.md)
- [数据布局](too-many-lists/unsafe-queue/layout.md) - [数据布局](too-many-lists/unsafe-queue/layout.md)
- [基本操作](too-many-lists/unsafe-queue/basics.md)
- [Rust 性能优化 todo](profiling/intro.md) - [Rust 性能优化 todo](profiling/intro.md)
- [深入内存 todo](profiling/memory/intro.md) - [深入内存 todo](profiling/memory/intro.md)

@ -295,11 +295,11 @@ fn main() {
## unsafe 解决循环引用 ## unsafe 解决循环引用
除了使用 Rust 标准库提供的这些类型,你还可以使用 `unsafe` 里的原生指针来解决这些棘手的问题,但是由于我们还没有讲解 `unsafe`,因此这里就不进行展开,只附上[源码链接](https://github.com/sunface/rust-algos/blob/fbcdccf3e8178a9039329562c0de0fd01a3372fb/src/unsafe/self-ref.md), 挺长的,需要耐心 o_o 除了使用 Rust 标准库提供的这些类型,你还可以使用 `unsafe` 里的指针来解决这些棘手的问题,但是由于我们还没有讲解 `unsafe`,因此这里就不进行展开,只附上[源码链接](https://github.com/sunface/rust-algos/blob/fbcdccf3e8178a9039329562c0de0fd01a3372fb/src/unsafe/self-ref.md), 挺长的,需要耐心 o_o
虽然 `unsafe` 不安全,但是在各种库的代码中依然很常见用它来实现自引用结构,主要优点如下: 虽然 `unsafe` 不安全,但是在各种库的代码中依然很常见用它来实现自引用结构,主要优点如下:
- 性能高,毕竟直接用原生指针操作 - 性能高,毕竟直接用指针操作
- 代码更简单更符合直觉: 对比下 `Option<Rc<RefCell<Node>>>` - 代码更简单更符合直觉: 对比下 `Option<Rc<RefCell<Node>>>`
## 总结 ## 总结

@ -160,9 +160,9 @@ fn main() {
} }
``` ```
在这里,我们在 `pointer_to_value` 中直接存储原生指针,而不是 Rust 的引用,因此不再受到 Rust 借用规则和生命周期的限制,而且实现起来非常清晰、简洁。但是缺点就是,通过指针获取值时需要使用 `unsafe` 代码。 在这里,我们在 `pointer_to_value` 中直接存储指针,而不是 Rust 的引用,因此不再受到 Rust 借用规则和生命周期的限制,而且实现起来非常清晰、简洁。但是缺点就是,通过指针获取值时需要使用 `unsafe` 代码。
当然,上面的代码你还能通过原生指针来修改 `String`,但是需要将 `*const` 修改为 `*mut` 当然,上面的代码你还能通过指针来修改 `String`,但是需要将 `*const` 修改为 `*mut`
```rust ```rust
#[derive(Debug)] #[derive(Debug)]
@ -230,7 +230,7 @@ use std::ptr::NonNull;
// 下面是一个自引用数据结构体,因为 slice 字段是一个指针,指向了 data 字段 // 下面是一个自引用数据结构体,因为 slice 字段是一个指针,指向了 data 字段
// 我们无法使用普通引用来实现,因为违背了 Rust 的编译规则 // 我们无法使用普通引用来实现,因为违背了 Rust 的编译规则
// 因此,这里我们使用了一个原生指针,通过 NonNull 来确保它不会为 null // 因此,这里我们使用了一个指针,通过 NonNull 来确保它不会为 null
struct Unmovable { struct Unmovable {
data: String, data: String,
slice: NonNull<String>, slice: NonNull<String>,
@ -272,7 +272,7 @@ fn main() {
上面的代码也非常清晰,虽然使用了 `unsafe`,其实更多的是无奈之举,跟之前的 `unsafe` 实现完全不可同日而语。 上面的代码也非常清晰,虽然使用了 `unsafe`,其实更多的是无奈之举,跟之前的 `unsafe` 实现完全不可同日而语。
其实 `Pin` 在这里并没有魔法,它也并不是实现自引用类型的主要原因,最关键的还是里面的原生指针的使用,而 `Pin` 起到的作用就是确保我们的值不会被移走,否则指针就会指向一个错误的地址! 其实 `Pin` 在这里并没有魔法,它也并不是实现自引用类型的主要原因,最关键的还是里面的指针的使用,而 `Pin` 起到的作用就是确保我们的值不会被移走,否则指针就会指向一个错误的地址!
## 使用 ouroboros ## 使用 ouroboros

@ -1,6 +1,6 @@
# 基于 Send 和 Sync 的线程安全 # 基于 Send 和 Sync 的线程安全
为何 Rc、RefCell 和原生指针不可以在多线程间使用?如何让原生指针可以在多线程使用?我们一起来探寻下这些问题的答案。 为何 Rc、RefCell 和裸指针不可以在多线程间使用?如何让裸指针可以在多线程使用?我们一起来探寻下这些问题的答案。
## 无法用于多线程的`Rc` ## 无法用于多线程的`Rc`
@ -80,7 +80,7 @@ unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
正是因为以上规则Rust 中绝大多数类型都实现了`Send`和`Sync`,除了以下几个(事实上不止这几个,只不过它们比较常见): 正是因为以上规则Rust 中绝大多数类型都实现了`Send`和`Sync`,除了以下几个(事实上不止这几个,只不过它们比较常见):
- 原生指针两者都没实现,因为它本身就没有任何安全保证 - 指针两者都没实现,因为它本身就没有任何安全保证
- `UnsafeCell`不是`Sync`,因此`Cell`和`RefCell`也不是 - `UnsafeCell`不是`Sync`,因此`Cell`和`RefCell`也不是
- `Rc`两者都没实现(因为内部的引用计数器不是线程安全的) - `Rc`两者都没实现(因为内部的引用计数器不是线程安全的)
@ -88,11 +88,11 @@ unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
**手动实现 `Send``Sync` 是不安全的**,通常并不需要手动实现 Send 和 Sync trait实现者需要使用`unsafe`小心维护并发安全保证。 **手动实现 `Send``Sync` 是不安全的**,通常并不需要手动实现 Send 和 Sync trait实现者需要使用`unsafe`小心维护并发安全保证。
至此,相关的概念大家已经掌握,但是我敢肯定,对于这两个滑不溜秋的家伙,大家依然会非常模糊,不知道它们该如何使用。那么我们来一起看看如何让原生指针可以在线程间安全的使用。 至此,相关的概念大家已经掌握,但是我敢肯定,对于这两个滑不溜秋的家伙,大家依然会非常模糊,不知道它们该如何使用。那么我们来一起看看如何让指针可以在线程间安全的使用。
## 为原生指针实现`Send` ## 为指针实现`Send`
上面我们提到原生指针既没实现`Send`,意味着下面代码会报错: 上面我们提到指针既没实现`Send`,意味着下面代码会报错:
```rust ```rust
use std::thread; use std::thread;
@ -128,7 +128,7 @@ fn main() {
此时,我们的指针已经可以欢快的在多线程间撒欢,以上代码很简单,但有一点需要注意:`Send`和`Sync`是`unsafe`特征,实现时需要用`unsafe`代码块包裹。 此时,我们的指针已经可以欢快的在多线程间撒欢,以上代码很简单,但有一点需要注意:`Send`和`Sync`是`unsafe`特征,实现时需要用`unsafe`代码块包裹。
## 为原生指针实现`Sync` ## 为指针实现`Sync`
由于`Sync`是多线程间共享一个值,大家可能会想这么实现: 由于`Sync`是多线程间共享一个值,大家可能会想这么实现:
@ -188,9 +188,9 @@ unsafe impl Sync for MyBox {}
## 总结 ## 总结
通过上面的两个原生指针的例子,我们了解了如何实现`Send`和`Sync`,以及如何只实现`Send`而不实现`Sync`,简单总结下: 通过上面的两个指针的例子,我们了解了如何实现`Send`和`Sync`,以及如何只实现`Send`而不实现`Sync`,简单总结下:
1. 实现`Send`的类型可以在线程间安全的传递其所有权, 实现`Sync`的类型可以在线程间安全的共享(通过引用) 1. 实现`Send`的类型可以在线程间安全的传递其所有权, 实现`Sync`的类型可以在线程间安全的共享(通过引用)
2. 绝大部分类型都实现了`Send`和`Sync`,常见的未实现的有:原生指针、`Cell`、`RefCell`、`Rc` 等 2. 绝大部分类型都实现了`Send`和`Sync`,常见的未实现的有:指针、`Cell`、`RefCell`、`Rc` 等
3. 可以为自定义类型实现`Send`和`Sync`,但是需要`unsafe`代码块 3. 可以为自定义类型实现`Send`和`Sync`,但是需要`unsafe`代码块
4. 可以为部分 Rust 中的类型实现`Send`、`Sync`,但是需要使用`newtype`,例如文中的原生指针例子 4. 可以为部分 Rust 中的类型实现`Send`、`Sync`,但是需要使用`newtype`,例如文中的指针例子

@ -1 +1 @@
# 原生指针、引用和智能指针 todo # 指针、引用和智能指针 todo

@ -136,7 +136,7 @@ error[E0499]: cannot borrow `*map` as mutable more than once at a time
不安全代码(`unsafe`)经常会凭空产生引用或生命周期,这些生命周期被称为是 **无界(unbound)** 的。 不安全代码(`unsafe`)经常会凭空产生引用或生命周期,这些生命周期被称为是 **无界(unbound)** 的。
无界生命周期往往是在解引用一个原生指针(裸指针 raw pointer)时产生的,换句话说,它是凭空产生的,因为输入参数根本就没有这个生命周期: 无界生命周期往往是在解引用一个指针(裸指针 raw pointer)时产生的,换句话说,它是凭空产生的,因为输入参数根本就没有这个生命周期:
```rust ```rust
fn f<'a, T>(x: *const T) -> &'a T { fn f<'a, T>(x: *const T) -> &'a T {

@ -57,7 +57,7 @@ fn get_memory_location() -> (usize, usize) {
} }
fn get_str_at_location(pointer: usize, length: usize) -> &'static str { fn get_str_at_location(pointer: usize, length: usize) -> &'static str {
// 使用原生指针需要 `unsafe{}` 语句块 // 使用指针需要 `unsafe{}` 语句块
unsafe { from_utf8_unchecked(from_raw_parts(pointer as *const u8, length)) } unsafe { from_utf8_unchecked(from_raw_parts(pointer as *const u8, length)) }
} }
@ -68,7 +68,7 @@ fn main() {
"The {} bytes at 0x{:X} stored: {}", "The {} bytes at 0x{:X} stored: {}",
length, pointer, message length, pointer, message
); );
// 如果大家想知道为何处理原生指针需要 `unsafe`,可以试着反注释以下代码 // 如果大家想知道为何处理指针需要 `unsafe`,可以试着反注释以下代码
// let message = get_str_at_location(1000, 10); // let message = get_str_at_location(1000, 10);
} }
``` ```

@ -42,17 +42,17 @@ fn main() {
} }
``` ```
上面代码中, `r1` 是一个原生指针(又称裸指针raw pointer),由于它具有破坏 Rust 内存安全的潜力,因此只能在 `unsafe` 代码块中使用,如果你去掉 `unsafe {}`,编译器会立刻报错。 上面代码中, `r1` 是一个指针(又称裸指针raw pointer),由于它具有破坏 Rust 内存安全的潜力,因此只能在 `unsafe` 代码块中使用,如果你去掉 `unsafe {}`,编译器会立刻报错。
言归正传, `unsafe` 能赋予我们 5 种超能力,这些能力在安全的 Rust 代码中是无法获取的: 言归正传, `unsafe` 能赋予我们 5 种超能力,这些能力在安全的 Rust 代码中是无法获取的:
- 解引用原生指针,就如上例所示 - 解引用指针,就如上例所示
- 调用一个 `unsafe` 或外部的函数 - 调用一个 `unsafe` 或外部的函数
- 访问或修改一个可变的[静态变量](https://course.rs/advance/global-variable.html#静态变量) - 访问或修改一个可变的[静态变量](https://course.rs/advance/global-variable.html#静态变量)
- 实现一个 `unsafe` 特征 - 实现一个 `unsafe` 特征
- 访问 `union` 中的字段 - 访问 `union` 中的字段
在本章中,我们将着重讲解原生指针和 FFI 的使用。 在本章中,我们将着重讲解指针和 FFI 的使用。
## unsafe 的安全保证 ## unsafe 的安全保证
@ -60,7 +60,7 @@ fn main() {
首先,`unsafe` 并不能绕过 Rust 的借用检查,也不能关闭任何 Rust 的安全检查规则,例如当你在 `unsafe` 中使用**引用**时,该有的检查一样都不会少。 首先,`unsafe` 并不能绕过 Rust 的借用检查,也不能关闭任何 Rust 的安全检查规则,例如当你在 `unsafe` 中使用**引用**时,该有的检查一样都不会少。
因此 `unsafe` 能给大家提供的也仅仅是之前的 5 种超能力,在使用这 5 种能力时,编译器才不会进行内存安全方面的检查,最典型的就是使用**原生指针**(引用和原生指针有很大的区别)。 因此 `unsafe` 能给大家提供的也仅仅是之前的 5 种超能力,在使用这 5 种能力时,编译器才不会进行内存安全方面的检查,最典型的就是使用**裸指针**(引用和裸指针有很大的区别)。
## 谈虎色变? ## 谈虎色变?

@ -2,24 +2,24 @@
古龙有一部小说,名为"七种兵器",其中每一种都精妙绝伦,令人闻风丧胆,而 `unsafe` 也有五种兵器,它们可以让你拥有其它代码无法实现的能力,同时它们也像七种兵器一样令人闻风丧胆,下面一起来看看庐山真面目。 古龙有一部小说,名为"七种兵器",其中每一种都精妙绝伦,令人闻风丧胆,而 `unsafe` 也有五种兵器,它们可以让你拥有其它代码无法实现的能力,同时它们也像七种兵器一样令人闻风丧胆,下面一起来看看庐山真面目。
## 解引用原生指针 ## 解引用指针
原生指针(raw pointer) 又称裸指针,在功能上跟引用类似,同时它也需要显式地注明可变性。但是又和引用有所不同,原生指针长这样: `*const T``*mut T`,它们分别代表了不可变和可变。 指针(raw pointer) 又称裸指针,在功能上跟引用类似,同时它也需要显式地注明可变性。但是又和引用有所不同,指针长这样: `*const T``*mut T`,它们分别代表了不可变和可变。
大家在之前学过 `*` 操作符,知道它可以用于解引用,但是在原生指针 `*const T` 中,这里的 `*` 只是类型名称的一部分,并没有解引用的含义。 大家在之前学过 `*` 操作符,知道它可以用于解引用,但是在指针 `*const T` 中,这里的 `*` 只是类型名称的一部分,并没有解引用的含义。
至此,我们已经学过三种类似指针的概念:引用、智能指针和原生指针。与前两者不同,原生指针: 至此,我们已经学过三种类似指针的概念:引用、智能指针和裸指针。与前两者不同,裸指针:
- 可以绕过 Rust 的借用规则,可以同时拥有一个数据的可变、不可变指针,甚至还能拥有多个可变的指针 - 可以绕过 Rust 的借用规则,可以同时拥有一个数据的可变、不可变指针,甚至还能拥有多个可变的指针
- 并不能保证指向合法的内存 - 并不能保证指向合法的内存
- 可以是 `null` - 可以是 `null`
- 没有实现任何自动的回收 (drop) - 没有实现任何自动的回收 (drop)
总之,原生指针跟 C 指针是非常像的,使用它需要以牺牲安全性为前提,但我们获得了更好的性能,也可以跟其它语言或硬件打交道。 总之,指针跟 C 指针是非常像的,使用它需要以牺牲安全性为前提,但我们获得了更好的性能,也可以跟其它语言或硬件打交道。
#### 基于引用创建原生指针 #### 基于引用创建指针
下面的代码**基于值的引用**同时创建了可变和不可变的原生指针: 下面的代码**基于值的引用**同时创建了可变和不可变的指针:
```rust ```rust
let mut num = 5; let mut num = 5;
@ -28,9 +28,9 @@ let r1 = &num as *const i32;
let r2 = &mut num as *mut i32; let r2 = &mut num as *mut i32;
``` ```
`as` 可以用于强制类型转换,在[之前章节](https://course.rs/basic/converse.html)中有讲解。在这里,我们将引用 `&num / &mut num` 强转为相应的原生指针 `*const i32 / *mut i32` `as` 可以用于强制类型转换,在[之前章节](https://course.rs/basic/converse.html)中有讲解。在这里,我们将引用 `&num / &mut num` 强转为相应的指针 `*const i32 / *mut i32`
细心的同学可能会发现,在这段代码中并没有 `unsafe` 的身影,原因在于:**创建原生指针是安全的行为,而解引用原生指针才是不安全的行为** : 细心的同学可能会发现,在这段代码中并没有 `unsafe` 的身影,原因在于:**创建裸指针是安全的行为,而解引用裸指针才是不安全的行为** :
```rust ```rust
fn main() { fn main() {
@ -44,16 +44,16 @@ fn main() {
} }
``` ```
#### 基于内存地址创建原生指针 #### 基于内存地址创建指针
在上面例子中,我们基于现有的引用来创建原生指针,这种行为是很安全的。但是接下来的方式就不安全了: 在上面例子中,我们基于现有的引用来创建指针,这种行为是很安全的。但是接下来的方式就不安全了:
```rust ```rust
let address = 0x012345usize; let address = 0x012345usize;
let r = address as *const i32; let r = address as *const i32;
``` ```
这里基于一个内存地址来创建原生指针,可以想像,这种行为是相当危险的。试图使用任意的内存地址往往是一种未定义的行为(undefined behavior),因为该内存地址有可能存在值,也有可能没有,就算有值,也大概率不是你需要的值。 这里基于一个内存地址来创建指针,可以想像,这种行为是相当危险的。试图使用任意的内存地址往往是一种未定义的行为(undefined behavior),因为该内存地址有可能存在值,也有可能没有,就算有值,也大概率不是你需要的值。
同时编译器也有可能会优化这段代码,会造成没有任何内存访问发生,甚至程序还可能发生段错误(segmentation fault)。**总之,你几乎没有好的理由像上面这样实现代码,虽然它是可行的**。 同时编译器也有可能会优化这段代码,会造成没有任何内存访问发生,甚至程序还可能发生段错误(segmentation fault)。**总之,你几乎没有好的理由像上面这样实现代码,虽然它是可行的**。
@ -82,7 +82,7 @@ fn main() {
"The {} bytes at 0x{:X} stored: {}", "The {} bytes at 0x{:X} stored: {}",
length, pointer, message length, pointer, message
); );
// 如果大家想知道为何处理原生指针需要 `unsafe`,可以试着反注释以下代码 // 如果大家想知道为何处理指针需要 `unsafe`,可以试着反注释以下代码
// let message = get_str_at_location(1000, 10); // let message = get_str_at_location(1000, 10);
} }
``` ```
@ -100,13 +100,13 @@ unsafe {
} }
``` ```
使用 `*` 可以对原生指针进行解引用,由于该指针的内存安全性并没有任何保证,因此我们需要使用 `unsafe` 来包裹解引用的逻辑(切记,`unsafe` 语句块的范围一定要尽可能的小,具体原因在上一章节有讲)。 使用 `*` 可以对指针进行解引用,由于该指针的内存安全性并没有任何保证,因此我们需要使用 `unsafe` 来包裹解引用的逻辑(切记,`unsafe` 语句块的范围一定要尽可能的小,具体原因在上一章节有讲)。
以上代码另一个值得注意的点就是:除了使用 `as` 来显式的转换,我们还使用了隐式的转换方式 `let c: *const i32 = &a;`。在实际使用中,我们建议使用 `as` 来转换,因为这种显式的方式更有助于提醒用户:你在使用的指针是原生指针,需要小心。 以上代码另一个值得注意的点就是:除了使用 `as` 来显式的转换,我们还使用了隐式的转换方式 `let c: *const i32 = &a;`。在实际使用中,我们建议使用 `as` 来转换,因为这种显式的方式更有助于提醒用户:你在使用的指针是指针,需要小心。
#### 基于智能指针创建原生指针 #### 基于智能指针创建指针
还有一种创建原生指针的方式,那就是基于智能指针来创建: 还有一种创建指针的方式,那就是基于智能指针来创建:
```rust ```rust
let a: Box<i32> = Box::new(10); let a: Box<i32> = Box::new(10);
@ -118,9 +118,9 @@ let c: *const i32 = Box::into_raw(a);
#### 小结 #### 小结
像之前代码演示的那样,使用原生指针可以让我们创建两个可变指针都指向同一个数据,如果使用安全的 Rust你是无法做到这一点的违背了借用规则编译器会对我们进行无情的阻止。因此原生指针可以绕过借用规则,但是由此带来的数据竞争问题,就需要大家自己来处理了,总之,需要小心! 像之前代码演示的那样,使用指针可以让我们创建两个可变指针都指向同一个数据,如果使用安全的 Rust你是无法做到这一点的违背了借用规则编译器会对我们进行无情的阻止。因此指针可以绕过借用规则,但是由此带来的数据竞争问题,就需要大家自己来处理了,总之,需要小心!
既然这么危险,为何还要使用原生指针?除了之前提到的性能等原因,还有一个重要用途就是跟 `C` 语言的代码进行交互( FFI ),在讲解 FFI 之前,先来看看如何调用 unsafe 函数或方法。 既然这么危险,为何还要使用指针?除了之前提到的性能等原因,还有一个重要用途就是跟 `C` 语言的代码进行交互( FFI ),在讲解 FFI 之前,先来看看如何调用 unsafe 函数或方法。
## 调用 unsafe 函数或方法 ## 调用 unsafe 函数或方法
@ -223,13 +223,13 @@ fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
相比安全实现,这段代码就显得没那么好理解了,甚至于我们还需要像 C 语言那样,通过指针地址的偏移去控制数组的分割。 相比安全实现,这段代码就显得没那么好理解了,甚至于我们还需要像 C 语言那样,通过指针地址的偏移去控制数组的分割。
- `as_mut_ptr` 会返回指向 `slice` 首地址的原生指针 `*mut i32` - `as_mut_ptr` 会返回指向 `slice` 首地址的指针 `*mut i32`
- `slice::from_raw_parts_mut` 函数通过指针和长度来创建一个新的切片,简单来说,该切片的初始地址是 `ptr`,长度为 `mid` - `slice::from_raw_parts_mut` 函数通过指针和长度来创建一个新的切片,简单来说,该切片的初始地址是 `ptr`,长度为 `mid`
- `ptr.add(mid)` 可以获取第二个切片的初始地址,由于切片中的元素是 `i32` 类型,每个元素都占用了 4 个字节的内存大小,因此我们不能简单的用 `ptr + mid` 来作为初始地址,而应该使用 `ptr + 4 * mid`,但是这种使用方式并不安全,因此 `.add` 方法是最佳选择 - `ptr.add(mid)` 可以获取第二个切片的初始地址,由于切片中的元素是 `i32` 类型,每个元素都占用了 4 个字节的内存大小,因此我们不能简单的用 `ptr + mid` 来作为初始地址,而应该使用 `ptr + 4 * mid`,但是这种使用方式并不安全,因此 `.add` 方法是最佳选择
由于 `slice::from_raw_parts_mut` 使用原生指针作为参数,因此它是一个 `unsafe fn`,我们在使用它时,就必须用 `unsafe` 语句块进行包裹,类似的,`.add` 方法也是如此(还是那句话,不要将无关的代码包含在 `unsafe` 语句块中)。 由于 `slice::from_raw_parts_mut` 使用指针作为参数,因此它是一个 `unsafe fn`,我们在使用它时,就必须用 `unsafe` 语句块进行包裹,类似的,`.add` 方法也是如此(还是那句话,不要将无关的代码包含在 `unsafe` 语句块中)。
部分同学可能会有疑问,那这段代码我们怎么保证 `unsafe` 中使用的原生指针 `ptr``ptr.add(mid)` 是合法的呢?秘诀就在于 `assert!(mid <= len);` ,通过这个断言,我们保证了原生指针一定指向了 `slice` 切片中的某个元素,而不是一个莫名其妙的内存地址。 部分同学可能会有疑问,那这段代码我们怎么保证 `unsafe` 中使用的指针 `ptr``ptr.add(mid)` 是合法的呢?秘诀就在于 `assert!(mid <= len);` ,通过这个断言,我们保证了指针一定指向了 `slice` 切片中的某个元素,而不是一个莫名其妙的内存地址。
再回到我们的主题:**虽然 split_at_mut 使用了 `unsafe`,但我们无需将其声明为 `unsafe fn`**,这种情况下就是使用安全的抽象包裹 `unsafe` 代码,这里的 `unsafe` 使用是非常安全的,因为我们从合法数据中创建了的合法指针。 再回到我们的主题:**虽然 split_at_mut 使用了 `unsafe`,但我们无需将其声明为 `unsafe fn`**,这种情况下就是使用安全的抽象包裹 `unsafe` 代码,这里的 `unsafe` 使用是非常安全的,因为我们从合法数据中创建了的合法指针。
@ -315,7 +315,7 @@ pub extern "C" fn call_from_c() {
## 实现 unsafe 特征 ## 实现 unsafe 特征
说实话,`unsafe` 的特征确实不多见,如果大家还记得的话,我们在之前的 [Send 和 Sync](https://course.rs/advance/concurrency-with-threads/send-sync.html#为原生指针实现sync) 章节中实现过 `unsafe` 特征 `Send` 说实话,`unsafe` 的特征确实不多见,如果大家还记得的话,我们在之前的 [Send 和 Sync](https://course.rs/advance/concurrency-with-threads/send-sync.html#为指针实现sync) 章节中实现过 `unsafe` 特征 `Send`
之所以会有 `unsafe` 的特征,是因为该特征至少有一个方法包含有编译器无法验证的内容。`unsafe` 特征的声明很简单: 之所以会有 `unsafe` 的特征,是因为该特征至少有一个方法包含有编译器无法验证的内容。`unsafe` 特征的声明很简单:
@ -333,7 +333,7 @@ fn main() {}
通过 `unsafe impl` 的使用,我们告诉编译器:相应的正确性由我们自己来保证。 通过 `unsafe impl` 的使用,我们告诉编译器:相应的正确性由我们自己来保证。
再回到刚提到的 `Send` 特征,若我们的类型中的所有字段都实现了 `Send` 特征,那该类型也会自动实现 `Send`。但是如果我们想要为某个类型手动实现 `Send` ,例如为原生指针,那么就必须使用 `unsafe`,相关的代码在之前的链接中也有,大家可以移步查看。 再回到刚提到的 `Send` 特征,若我们的类型中的所有字段都实现了 `Send` 特征,那该类型也会自动实现 `Send`。但是如果我们想要为某个类型手动实现 `Send` ,例如为指针,那么就必须使用 `unsafe`,相关的代码在之前的链接中也有,大家可以移步查看。
总之,`Send` 特征标记为 `unsafe` 是因为 Rust 无法验证我们的类型是否能在线程间安全的传递,因此就需要通过 `unsafe` 来告诉编译器,它无需操心,剩下的交给我们自己来处理。 总之,`Send` 特征标记为 `unsafe` 是因为 Rust 无法验证我们的类型是否能在线程间安全的传递,因此就需要通过 `unsafe` 来告诉编译器,它无需操心,剩下的交给我们自己来处理。

@ -26,7 +26,7 @@
- `match` - 模式匹配 - `match` - 模式匹配
- `mod` - 定义一个模块 - `mod` - 定义一个模块
- `move` - 使闭包获取其所捕获项的所有权 - `move` - 使闭包获取其所捕获项的所有权
- `mut` - 在引用、原生指针或模式绑定中使用,表明变量是可变的 - `mut` - 在引用、指针或模式绑定中使用,表明变量是可变的
- `pub` - 表示结构体字段、`impl` 块或模块的公共可见性 - `pub` - 表示结构体字段、`impl` 块或模块的公共可见性
- `ref` - 通过引用绑定 - `ref` - 通过引用绑定
- `return` - 从函数中返回 - `return` - 从函数中返回

@ -25,7 +25,7 @@
| `*` | `expr * expr` | 算术乘法 | `Mul` | | `*` | `expr * expr` | 算术乘法 | `Mul` |
| `*=` | `var *= expr` | 算术乘法与赋值 | `MulAssign` | | `*=` | `var *= expr` | 算术乘法与赋值 | `MulAssign` |
| `*` | `*expr` | 解引用 | | | `*` | `*expr` | 解引用 | |
| `*` | `*const type`, `*mut type` | 原生指针 | | | `*` | `*const type`, `*mut type` | 指针 | |
| `+` | `trait + trait`, `'a + trait` | 复合类型限制 | | | `+` | `trait + trait`, `'a + trait` | 复合类型限制 | |
| `+` | `expr + expr` | 算术加法 | `Add` | | `+` | `expr + expr` | 算术加法 | `Add` |
| `+=` | `var += expr` | 算术加法与赋值 | `AddAssign` | | `+=` | `var += expr` | 算术加法与赋值 | `AddAssign` |

@ -16,7 +16,7 @@ struct SelfRef {
} }
``` ```
在上面的结构体中,`pointer_to_value` 是一个原生指针,指向第一个字段 `value` 持有的字符串 `String` 。很简单对吧?现在考虑一个情况, 若`String` 被移动了怎么办? 在上面的结构体中,`pointer_to_value` 是一个指针,指向第一个字段 `value` 持有的字符串 `String` 。很简单对吧?现在考虑一个情况, 若`String` 被移动了怎么办?
此时一个致命的问题就出现了:新的字符串的内存地址变了,而 `pointer_to_value` 依然指向之前的地址,一个重大 bug 就出现了! 此时一个致命的问题就出现了:新的字符串的内存地址变了,而 `pointer_to_value` 依然指向之前的地址,一个重大 bug 就出现了!
@ -168,7 +168,7 @@ impl Test {
} }
``` ```
`Test` 提供了方法用于获取字段 `a``b` 的值的引用。这里`b` 是 `a` 的一个引用,但是我们并没有使用引用类型而是用了原生指针原因是Rust 的借用规则不允许我们这样用,因为不符合生命周期的要求。 此时的 `Test` 就是一个自引用结构体。 `Test` 提供了方法用于获取字段 `a``b` 的值的引用。这里`b` 是 `a` 的一个引用,但是我们并没有使用引用类型而是用了指针原因是Rust 的借用规则不允许我们这样用,因为不符合生命周期的要求。 此时的 `Test` 就是一个自引用结构体。
如果不移动任何值,那么上面的例子将没有任何问题,例如: 如果不移动任何值,那么上面的例子将没有任何问题,例如:

@ -282,11 +282,11 @@ impl<T> Clone for Container<T> {
你以为你之前凝视的是深渊吗?不,你凝视的只是深渊的大门。 `mem::transmute_copy<T, U>` 才是真正的深渊,它比之前的还要更加危险和不安全。它从 `T` 类型中拷贝出 `U` 类型所需的字节数,然后转换成 `U``mem::transmute` 尚有大小检查,能保证两个数据的内存大小一致,现在这哥们干脆连这个也丢了,只不过 `U` 的尺寸若是比 `T` 大,会是一个未定义行为。 你以为你之前凝视的是深渊吗?不,你凝视的只是深渊的大门。 `mem::transmute_copy<T, U>` 才是真正的深渊,它比之前的还要更加危险和不安全。它从 `T` 类型中拷贝出 `U` 类型所需的字节数,然后转换成 `U``mem::transmute` 尚有大小检查,能保证两个数据的内存大小一致,现在这哥们干脆连这个也丢了,只不过 `U` 的尺寸若是比 `T` 大,会是一个未定义行为。
当然,你也可以通过原生指针转换和 `unions` (todo!)获得所有的这些功能,但是你将无法获得任何编译提示或者检查。原生指针转换和 `unions` 也不是魔法,无法逃避上面说的规则。 当然,你也可以通过指针转换和 `unions` (todo!)获得所有的这些功能,但是你将无法获得任何编译提示或者检查。指针转换和 `unions` 也不是魔法,无法逃避上面说的规则。
`transmute` 虽然危险,但作为一本工具书,知识当然要全面,下面列举两个有用的 `transmute` 应用场景 :)。 `transmute` 虽然危险,但作为一本工具书,知识当然要全面,下面列举两个有用的 `transmute` 应用场景 :)。
- 将原生指针变成函数指针: - 将指针变成函数指针:
```rust ```rust
fn foo() -> i32 { fn foo() -> i32 {
@ -295,7 +295,7 @@ fn foo() -> i32 {
let pointer = foo as *const (); let pointer = foo as *const ();
let function = unsafe { let function = unsafe {
// 将原生指针转换为函数指针 // 将指针转换为函数指针
std::mem::transmute::<*const (), fn() -> i32>(pointer) std::mem::transmute::<*const (), fn() -> i32>(pointer)
}; };
assert_eq!(function(), 0); assert_eq!(function(), 0);

@ -0,0 +1,5 @@
# 基本操作
> 本章节的代码中有一个隐藏的 bug因为它藏身于 unsafe 中,因此不会导致报错,我们会在后续章节解决这个问题,所以,请不要在生产环境使用此处的代码
在开始之前,大家需要先了解 unsafe 的[相关知识](https://course.rs/advance/unsafe/intro.html)

@ -1,8 +1,10 @@
# ChangeLog # ChangeLog
记录一些值得注意的变更。 记录一些值得注意的变更。
## 2022-03-17 ## 2022-03-17
- 新增章节: [不错的unsafe队列-数据布局](https://course.rs/too-many-lists/unsafe-queue/layout.html)
- 新增章节: [deque-迭代器](https://course.rs/too-many-lists/deque/iterator.html) - 新增章节: [deque-迭代器](https://course.rs/too-many-lists/deque/iterator.html)
- 新增章节: [deque-最终代码](https://course.rs/too-many-lists/deque/final-code.html) - 新增章节: [deque-最终代码](https://course.rs/too-many-lists/deque/final-code.html)

Loading…
Cancel
Save