Rust中的自引用结构:逐步教程

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

为什么Rust不允许你存储对自身的引用:通过示例进行分析

\n\n

想象一下这种情况:你在用Rust编写程序,需要创建一个结构体,它既存储某些数据,又存储对这些数据的引用。这很合理,对吧?当然!但Rust编译器突然报错,让你感到困惑。熟悉吗?这个问题是Stack Overflow上最热门的问题之一(有数千次浏览),今天我们将从头到尾彻底分析它。

\n\n

在这个逐步教程中,我们不仅会理解为什么不能在Rust结构体中同时存储值和引用,还会学会用安全的方法绕过这个限制。开始吧!

\n\n

问题:天真的尝试

\n\n

假设我们想创建一个结构体,它存储一个字符串及其第一个字母(对第一个字符的引用)。代码如下:

\n\n
struct MyStruct {\n    data: String,\n    first_char: &str,  // 对data的引用\n}\n\nfn main() {\n    let s = String::from("Hello");\n    let first = &s[0..1];  // 对第一个字符的引用\n    \n    let my_struct = MyStruct {\n        data: s,\n        first_char: first,  // 尝试保存引用\n    };\n}\n
\n\n

编译器会报错,这让许多新手感到困惑。让我们分析一下到底哪里出了问题。

\n\n

为什么Rust禁止自引用结构?

\n\n

问题的根源在于Rust的所有权和借用系统。当你将值s移动到字段data中时,它会在内存中物理复制到新的位置。而引用first仍然指向旧的地址,这个地址现在已经无效了。

\n\n

在Rust中,每个引用在其整个生命周期内都必须有效。Rust的自引用结构违反了这一规则,因为引用和值存在于同一个对象中,当结构体被移动时,引用就变成了悬垂引用。

\n\n

底层发生了什么?

\n\n

当你编写这样的代码时:

\n\n
let s = String::from("Hello");\nlet first = &s[0..1];\nlet my_struct = MyStruct { data: s, first_char: first };\n
\n\n

会发生以下情况:

\n
    \n
  1. s拥有堆上的字符串"Hello"。
  2. \n
  3. first是对该字符串一部分的引用。
  4. \n
  5. 创建结构体时,data: s将字符串的所有权转移给结构体。字符串可能会在内存中移动。
  6. \n
  7. 引用first仍然指向旧地址——这是一个悬垂引用。Rust不允许这种情况发生。
  8. \n
\n\n

Rust的借用检查器看到了这种潜在的不安全性,并在编译阶段阻止了代码。这不是一个bug,而是一个特性,它避免了运行时的许多问题。

\n\n

需要自引用结构的实际场景

\n\n

尽管有禁止,这类结构体有时还是很有用的。以下是典型情况:

\n\n
    \n
  • 解析器和AST:当树节点相互引用时。
  • \n
  • 游戏引擎:包含对其组件引用的实体。
  • \n
  • 网络协议:带有内部指针的缓冲区。
  • \n
  • 插件系统:当对象包含对其配置的引用时。
  • \n
\n\n

好消息是:有安全的方法可以绕过这个限制!

\n\n

解决方案1:使用索引代替引用(最简单的方法)

\n\n

最简单且安全的方法是存储索引或键,而不是引用。用usize(第一个字符的位置)代替&str。

\n\n
struct MyStruct {\n    data: String,\n    first_char_index: usize,  // 存储位置,而不是引用\n}\n\nimpl MyStruct {\n    fn first_char(&self) -> &                

博客

书籍推荐