基础

好了,回到最基本的问题,我们如何构建我们的列表?

在我们这么做之前:

impl<T> List<T> {
    pub fn new() -> Self {
        List { head: None, tail: None }
    }
}

但是我们不再使用 Option 作为 tail:

> cargo build

error[E0308]: mismatched types
  --> src/fifth.rs:15:34
   |
15 |         List { head: None, tail: None }
   |                                  ^^^^ expected *-ptr, found enum `std::option::Option`
   |
   = note: expected type `*mut fifth::Node<T>`
              found type `std::option::Option<_>`

我们可以使用 Option,但是不像 Box,*mut 可以为空。这意味着它不能从空指针优化中获益。相反,我们将使用 null 表示 None。

那么我们如何得到一个空指针呢?有几种方法,但我更喜欢使用 std: : ptr::null_mut()。如果你愿意,你也可以使用 0 作为 *mut,但是这看起来太乱了。

嘘,编译器,我们很快就会用到它们。

好的,让我们继续写 push。这一次,在我们插入之后,我们不是抓取一个 Option<&mut Node<T>> ,而是直接抓取一个 *mut Node<T> 到 Box 的内部。我们知道我们可以很好地做到这一点,因为 Box 里的东西有一个稳定的地址,即使我们把 Box移来移去。当然,这是不安全的,因为如果我们只是删除 Box,我们将有一个指针指向释放的内存。

如何从普通指针生成原始指针?强制转换!如果一个变量被声明为原始指针,则一个普通引用将强制进入该变量:

我们拥有所需的所有信息。我们可以将我们的代码翻译成之前的参考版本:

啊? 我们有一个指向 Node 的指针,为什么我们不能得到下一个字段?

使用原始指针时,Rust有点混蛋。 要访问原始指针的内容,它坚持要求我们手动取消对它们的引用,因为这是一种不安全的操作。 因此,让我们这样做:

操作符优先级。

这应该不会这么难。

还记得我说过的Unsafe Rust就像是Safe Rust的FFI语言吗? 好吧,编译器希望我们明确界定执行此FFI处理的位置。 我们有两个选择。 首先,我们可以将整个函数标记为不安全,在这种情况下,它会变成不安全的Rust函数,并且只能在不安全的上下文中调用。 这不是很好,因为我们希望我们的列表可以安全使用。 其次,我们可以在函数内部编写一个不安全的块,以划定FFI边界。 这声明整体功能是安全的。 让我们做一个:

耶!

到目前为止,这是我们唯一必须编写不安全块的地方,这很有趣。 我们到处都是原始指针,这是怎么回事?

事实证明,当涉及到不安全时,Rust是一个庞大的规则书呆子。我们非常合理地希望最大限度地利用 Safe Rust 程序,因为这些程序我们可以更有信心。为了做到这一点,Rust 小心地划出一个不安全的最小表面积。请注意,我们使用原始指针的所有其他地方都在分配它们,或者只是观察它们是否为null。

如果您从未真正取消引用原始指针,那是完全安全的事情。 您只是在读取和写入一个整数! 唯一真正遇到麻烦的是原始指针是否要取消引用。 因此,Rust说只有操作是不安全的,其他一切都是完全安全的。

超级迂腐但技术上是正确的。

然而,这提出了一个有趣的问题: 尽管我们应该用不安全的块来限定不安全的范围,但它实际上取决于在块之外建立的状态。甚至在功能之外!

这就是我所说的不安全的污点。 一旦在模块中使用不安全,整个模块便会受到不安全的污染。 必须正确编写所有内容,以确保对于不安全的代码保持不变。

这种污染是可控的,因为隐私。在我们的模块之外,我们的所有结构字段都是完全私有的,所以没有其他人可以以任意的方式干扰我们的状态。只要我们公开的 api 没有组合导致糟糕的事情发生,对于外部观察者来说,我们所有的代码都是安全的!事实上,这和 FFI 的案子没什么不同。只要 python 数学库公开了一个安全的接口,就没有人需要关心它是否外壳到 C 中。

无论如何,让我们继续讨论  pop,它几乎是逐字逐句的引用版本:

我们又看到了另一个安全是有状态的。如果我们不能使这个函数中的尾部指针为空,我们就不会看到任何问题。然而,随后的push调用将开始写入悬垂的尾部!

让我们来测试一下:

这只是堆栈测试,但是预期的 pop 结果反过来了。我还在结尾添加了一些额外的步骤,以确保 pop 中的尾指针损坏情况不会发生。

Last updated

Was this helpful?